Эффективный (по Ryzen) способ извлечь нечетные элементы __m256 в __m128?

Есть ли собственный или другой эффективный способ переупаковки 32-разрядных 32-разрядных компонентов 64-разрядных компонентов регистра AVX в регистр SSE? Решение с использованием AVX2 в порядке.

До сих пор я использую следующий код, но профайлер говорит, что он медленный на Ryzen 1800X:

// Global constant
const __m256i gHigh32Permute = _mm256_set_epi32(0, 0, 0, 0, 7, 5, 3, 1);

// ...

// function code
__m256i x = /* computed here */;
const __m128i high32 = _mm256_castsi256_si128(_mm256_permutevar8x32_epi32(x),
  gHigh32Permute); // This seems to take 3 cycles

Ответ 1

В Intel ваш код будет оптимальным. Одна инструкция 1-юп - лучшее, что вы получите. (За исключением того, что вы можете использовать vpermps, чтобы избежать риска задержек при включении int/FP, если ваш вектор ввода был создан командой pd, а не нагрузкой или чем-то другим. Использование результата перетасовки FP в качестве входа для целых инструкций, как правило, отлично подходит для Intel, но я менее уверен в том, что я буду кормить результат инструкции FP для целочисленного тасования.)

Несмотря на то, что при настройке для Intel вы можете попробовать изменить окружающий код, чтобы вы могли перетасовать нижние 64 бита каждой полосы 128b, чтобы избежать использования перетасовки переходов. (Тогда вы могли бы просто использовать vshufps ymm, или при настройке для KNL, vpermilps, поскольку 2-вход vshufps медленнее.)

С AVX512, _mm256_cvtepi64_epi32 (vpmovqd), который упаковывает элементы по полосам с усечением.


На Рызене переходы между перекрестками медленнее. Agner Fog не имеет чисел для vpermd, но он перечисляет vpermps (который, вероятно, использует одно и то же оборудование внутри) в 3 раза, 5 с задержка, по одной на 4с пропускную способность.

vextractf128 xmm, ymm, 1 очень эффективен для Ryzen (1c latency, 0,33c пропускная способность), что неудивительно, поскольку он отслеживает 256b регистров как две 128b половин уже. shufps также эффективен (задержка 1 с, пропускная способность 0,5 с) и позволит вам перетасовать два регистра 128b в желаемый результат.

Это также экономит вам 2 регистра для 2 vpermps тасовых масок, которые вам больше не нужны.

Поэтому я бы предложил:

__m256d x = /* computed here */;

// Tuned for Ryzen.  Sub-optimal on Intel
__m128 hi = _mm_castpd_ps(_mm256_extractf128_pd(x, 1));
__m128 lo = _mm_castpd_ps(_mm256_castpd256_pd128(x));
__m128 odd  = _mm_shuffle_ps(lo, hi, _MM_SHUFFLE(3,1,3,1));
__m128 even = _mm_shuffle_ps(lo, hi, _MM_SHUFFLE(2,0,2,0));

В Intel, используя 3 перетасовки вместо 2, вы получаете 2/3rds оптимальной пропускной способности, с 1 с дополнительной задержкой для первого результата.