Вот исходный код SSSE3, в который добавлены некоторые мои собственные диспетчеризации.
void DspConvertPcm(f32* pOutBuffer, const s24* pInBuffer, size_t totalSampleCount)
{
constexpr f32 fScale = static_cast<f32>(1.0 / (1<<23));
size_t i = 0;
size_t vecSampleCount = 0;
#if defined(SFTL_SSE2)
if (CpuInfo::GetSupports_SIMD_I32x8())
{
vecSampleCount = DspConvertPcm_AVX2(pOutBuffer, pInBuffer, totalSampleCount);
}
else
if (CpuInfo::GetSupports_SSE3())
{
const auto vScale = _mm_set1_ps(fScale);
const auto mask = _mm_setr_epi8(-1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11);
constexpr size_t step = 16;
vecSampleCount = (totalSampleCount / step) * step;
for (; i < vecSampleCount; i += step)
{
const auto* pSrc = reinterpret_cast<const __m128i*>(pInBuffer + i);
auto* pDst = pOutBuffer + i;
const auto sa = _mm_loadu_si128(pSrc + 0);
const auto sb = _mm_loadu_si128(pSrc + 1);
const auto sc = _mm_loadu_si128(pSrc + 2);
const auto da = _mm_srai_epi32(_mm_shuffle_epi8(sa, mask), 8);
const auto db = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sb, sa, 12), mask), 8);
const auto dc = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sb, 8), mask), 8);
const auto dd = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sc, 4), mask), 8);
// Convert to float and store
_mm_storeu_ps(pDst + 0, _mm_mul_ps(_mm_cvtepi32_ps(da), vScale));
_mm_storeu_ps(pDst + 4, _mm_mul_ps(_mm_cvtepi32_ps(db), vScale));
_mm_storeu_ps(pDst + 8, _mm_mul_ps(_mm_cvtepi32_ps(dc), vScale));
_mm_storeu_ps(pDst + 12, _mm_mul_ps(_mm_cvtepi32_ps(dd), vScale));
}
}
#endif
for (; i < totalSampleCount; i += 1)
{
pOutBuffer[i] = (static_cast<s32>(pInBuffer[i])) * fScale;
}
}
Если присутствует AVX2, он вызовет DspConvertPcm_AVX2, который выглядит так:
size_t DspConvertPcm_AVX2(f32* pOutBuffer, const s24* pInBuffer, size_t totalSampleCount)
{
SFTL_ASSERT(CpuInfo::GetSupports_SIMD_I32x8());
constexpr f32 fScale = static_cast<f32>(1.0 / (1 << 23));
const auto vScale = _mm256_set1_ps(fScale);
auto fnDo16Samples = [vScale](f32* pOutBuffer, const s24* pInBuffer)
{
const auto vScaleSSE = _mm256_castps256_ps128(vScale);
const auto mask = _mm_setr_epi8(-1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11);
const auto* pSrc = reinterpret_cast<const __m128i*>(pInBuffer);
auto* pDst = pOutBuffer;
const auto sa = _mm_loadu_si128(pSrc + 0);
const auto sb = _mm_loadu_si128(pSrc + 1);
const auto sc = _mm_loadu_si128(pSrc + 2);
const auto da = _mm_srai_epi32(_mm_shuffle_epi8(sa, mask), 8);
const auto db = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sb, sa, 12), mask), 8);
const auto dc = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sb, 8), mask), 8);
const auto dd = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sc, 4), mask), 8);
// Convert to float and store
_mm_storeu_ps(pDst + 0, _mm_mul_ps(_mm_cvtepi32_ps(da), vScaleSSE));
_mm_storeu_ps(pDst + 4, _mm_mul_ps(_mm_cvtepi32_ps(db), vScaleSSE));
_mm_storeu_ps(pDst + 8, _mm_mul_ps(_mm_cvtepi32_ps(dc), vScaleSSE));
_mm_storeu_ps(pDst + 12, _mm_mul_ps(_mm_cvtepi32_ps(dd), vScaleSSE));
};
// First 16 samples SSE style
fnDo16Samples(pOutBuffer, pInBuffer);
// Next samples do AVX, where each load will discard 4 bytes at the start and end of each load
constexpr size_t step = 16;
const size_t vecSampleCount = ((totalSampleCount / step) * step) - 16;
{
const auto mask = _mm256_setr_epi8(-1, 4, 5, 6, -1, 7, 8, 9, -1, 10, 11, 12, -1, 13, 14, 15, -1, 16, 17, 18, -1, 19, 20, 21, -1, 22, 23, 24, -1, 25, 26, 27);
for (size_t i = 16; i < vecSampleCount; i += step)
{
const byte* pByteBuffer = reinterpret_cast<const byte*>(pInBuffer + i);
auto* pDst = pOutBuffer + i;
const auto vs24_00_07 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(pByteBuffer - 4));
const auto vs24_07_15 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(pByteBuffer - 24));
const auto vf32_00_07 = _mm256_srai_epi32(_mm256_shuffle_epi8(vs24_00_07, mask), 8);
const auto vf32_07_15 = _mm256_srai_epi32(_mm256_shuffle_epi8(vs24_07_15, mask), 8);
// Convert to float and store
_mm256_storeu_ps(pDst + 0, _mm256_mul_ps(_mm256_cvtepi32_ps(vf32_00_07), vScale));
_mm256_storeu_ps(pDst + 8, _mm256_mul_ps(_mm256_cvtepi32_ps(vf32_00_07), vScale));
}
}
// Last 16 samples SSE style
fnDo16Samples(pOutBuffer + vecSampleCount, pInBuffer + vecSampleCount);
return vecSampleCount;
}
Обратите внимание, что я вручную развернул основной цикл AVX2, чтобы попытаться немного ускорить его, но на самом деле это не имело большого значения.
С таймером, установленным непосредственно перед вызовом DspConvertPcm, который обрабатывает 1024 выборки за раз, среднее время обработки здесь с включенным путем кода AVX2 будет варьироваться от 2,6 до 3,0 микросекунд. С другой стороны, если я отключу путь кода AVX2, среднее время колеблется около 2,0 микросекунд.
С другой стороны, включение кодирования VEX с помощью /arch:AVX2 не дало мне постоянного прироста производительности, о котором я заявлял ранее, так что это, должно быть, было случайностью.
Этот тест был выполнен на процессоре Haswell core i7-6700HQ с частотой 2,6 ГГц с использованием компилятора MSVC по умолчанию в Visual Studio 15.9.5 с включенной оптимизацией для скорости и использованием /fp:fast.
person
Kumputer
schedule
04.02.2019
pshufb
все еще может быть лучшим выбором. Но вам следует подумать о невыровненных загрузках вместо использования_mm_alignr_epi8
, потому что современный Intel будет узким местом при одном перетасовке за такт, прежде чем он станет узким местом при одном хранилище за такт с вашим кодом, который выполняет несколько перетасовок на хранилище. - person Peter Cordes   schedule 12.02.2018