Я выполняю разрозненное чтение 8-битных данных из файла (де-чередование 64-канального волнового файла). Затем я объединяю их в единый поток байтов. Проблема, с которой я сталкиваюсь, связана с моей реконструкцией данных для записи.
В основном я читаю 16 байтов, а затем создаю их в одну переменную __m128i, а затем использую _mm_stream_ps для записи значения обратно в память. Однако у меня есть некоторые странные результаты производительности.
В моей первой схеме я использую встроенную функцию _mm_set_epi8 для установки __m128i следующим образом:
const __m128i packedSamples = _mm_set_epi8( sample15, sample14, sample13, sample12, sample11, sample10, sample9, sample8,
sample7, sample6, sample5, sample4, sample3, sample2, sample1, sample0 );
По сути, я оставляю компилятору решать, как его оптимизировать для достижения наилучшей производительности. Это дает ХУДШУЮ производительность. МОЙ тест выполняется примерно за 0,195 секунды.
Во-вторых, я попытался объединиться, используя 4 инструкции _mm_set_epi32, а затем упаковав их:
const __m128i samples0 = _mm_set_epi32( sample3, sample2, sample1, sample0 );
const __m128i samples1 = _mm_set_epi32( sample7, sample6, sample5, sample4 );
const __m128i samples2 = _mm_set_epi32( sample11, sample10, sample9, sample8 );
const __m128i samples3 = _mm_set_epi32( sample15, sample14, sample13, sample12 );
const __m128i packedSamples0 = _mm_packs_epi32( samples0, samples1 );
const __m128i packedSamples1 = _mm_packs_epi32( samples2, samples3 );
const __m128i packedSamples = _mm_packus_epi16( packedSamples0, packedSamples1 );
Это несколько улучшает производительность. Мой тест теперь выполняется за ~ 0,15 секунды. Кажется нелогичным, что производительность улучшится, сделав это, поскольку я предполагаю, что это именно то, что делает _mm_set_epi8 в любом случае...
Моя последняя попытка состояла в том, чтобы использовать немного кода, который у меня есть, из создания четырех CC старомодным способом (со сдвигами и т. д.), а затем помещая их в __m128i с помощью одного _mm_set_epi32.
const GCui32 samples0 = MakeFourCC( sample0, sample1, sample2, sample3 );
const GCui32 samples1 = MakeFourCC( sample4, sample5, sample6, sample7 );
const GCui32 samples2 = MakeFourCC( sample8, sample9, sample10, sample11 );
const GCui32 samples3 = MakeFourCC( sample12, sample13, sample14, sample15 );
const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );
Это дает еще ЛУЧШУЮ производительность. Запуск моего теста занимает ~ 0,135 секунды. Я действительно начинаю путаться.
Поэтому я попробовал простую систему чтения байтов записи байтов, и это немного быстрее, чем даже последний метод.
Так, что происходит? Все это кажется мне контринтуитивным.
Я рассматривал идею о том, что задержки происходят на _mm_stream_ps, потому что я предоставляю данные слишком быстро, но тогда я бы получил точно такие же результаты, что бы я ни делал. Возможно ли, что первые 2 метода означают, что 16 нагрузок не могут быть распределены по циклу, чтобы скрыть задержку? Если да, то почему это? Конечно, встроенная функция позволяет компилятору выполнять оптимизацию по своему усмотрению ... я думал, что в этом все дело ... Также, безусловно, выполнение 16 операций чтения и 16 операций записи будет намного медленнее, чем 16 операций чтения и 1 запись с кучей жонглирования SSE инструкции ... В конце концов, чтение и запись - это медленный бит!
Любой, у кого есть какие-либо идеи, что происходит, будет высоко оценен! :D
Изменить: в дополнение к комментарию ниже я прекратил предварительную загрузку байтов как константы и изменил их на это:
const __m128i samples0 = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
pSamples += channelStep4;
const __m128i samples1 = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
pSamples += channelStep4;
const __m128i samples2 = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
pSamples += channelStep4;
const __m128i samples3 = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
pSamples += channelStep4;
const __m128i packedSamples0 = _mm_packs_epi32( samples0, samples1 );
const __m128i packedSamples1 = _mm_packs_epi32( samples2, samples3 );
const __m128i packedSamples = _mm_packus_epi16( packedSamples0, packedSamples1 );
и это улучшило производительность до ~ 0,143 секунды. Sitll не так хорош, как прямая реализация C...
Изменить снова: лучшая производительность, которую я получаю до сих пор,
// Load the samples.
const GCui8 sample0 = *(pSamples + channelStep0);
const GCui8 sample1 = *(pSamples + channelStep1);
const GCui8 sample2 = *(pSamples + channelStep2);
const GCui8 sample3 = *(pSamples + channelStep3);
const GCui32 samples0 = Build32( sample0, sample1, sample2, sample3 );
pSamples += channelStep4;
const GCui8 sample4 = *(pSamples + channelStep0);
const GCui8 sample5 = *(pSamples + channelStep1);
const GCui8 sample6 = *(pSamples + channelStep2);
const GCui8 sample7 = *(pSamples + channelStep3);
const GCui32 samples1 = Build32( sample4, sample5, sample6, sample7 );
pSamples += channelStep4;
// Load the samples.
const GCui8 sample8 = *(pSamples + channelStep0);
const GCui8 sample9 = *(pSamples + channelStep1);
const GCui8 sample10 = *(pSamples + channelStep2);
const GCui8 sample11 = *(pSamples + channelStep3);
const GCui32 samples2 = Build32( sample8, sample9, sample10, sample11 );
pSamples += channelStep4;
const GCui8 sample12 = *(pSamples + channelStep0);
const GCui8 sample13 = *(pSamples + channelStep1);
const GCui8 sample14 = *(pSamples + channelStep2);
const GCui8 sample15 = *(pSamples + channelStep3);
const GCui32 samples3 = Build32( sample12, sample13, sample14, sample15 );
pSamples += channelStep4;
const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );
_mm_stream_ps( pWrite + 0, *(__m128*)&packedSamples );
Это дает мне обработку примерно за 0,095 секунды, что значительно лучше. Я, кажется, не могу приблизиться к SSE, хотя ... Я все еще сбит с толку этим, но .. ho гул.
ah
,bh
(хотя я не знаю, как это работает аппаратно). В противном случае shift+or — это две очень быстрые инструкции, а move+move+pack — три, где каждое перемещение связывает два регистровых файла (опять же, не уверен в деталях HW, которые различаются). Новый способ не так уж плох, просто старый способ эффективен. - person Potatoswatter   schedule 06.01.2010