تسبب الجزء السابق في مناقشة ساخنة ، تبين خلالها أن AVX / AVX2 موجود بالفعل في وحدات المعالجة المركزية لسطح المكتب ، وليس فقط AVX512. لذلك ، نحن مستمرون في التعرف على SIMD ، ولكن بالفعل مع الجزء الحديث - AVX. وكذلك سنحلل بعض التعليقات:
- هل
_mm256_load_si256
أبطأ من الوصول المباشر للذاكرة؟ - هل يؤثر استخدام أوامر AVX على سجلات SSE على السرعة؟
- هو
_popcnt
حقا بهذا السوء؟
قليلا عن AVX
AVX / AVX2 هو إصدار أقوى من SSE يمتد معظم عمليات SSE 128 بت إلى 256 بت ، بالإضافة إلى أنه يجلب عددًا من الإرشادات الجديدة.
من التفاصيل الدقيقة للتطبيق ، يمكننا تمييز أنه على مستوى المجمّع يستخدم AVX 3 وسيطات ، مما يسمح بعدم إتلاف البيانات في الأولين. يخزن SSE النتيجة في إحدى الوسائط.
يجب أن يؤخذ أيضًا في الاعتبار أنه من خلال العنونة المباشرة ، يجب محاذاة البيانات بمقدار 32 بايت ، وفي SSE ، والمحاذاة بنسبة 16.
نسخة معززة من المؤشر
التغييرات:
- تمت زيادة عدد العناصر بمقدار 10000 مرة (حتى 10،240،000) ، حتى لا تنسجم مع ذاكرة التخزين المؤقت للمعالج.
- تم تغيير المحاذاة من 16 بايت إلى 32 لدعم AVX.
- تطبيقات AVX المضافة على غرار SSE.
رمز المعيار #include <benchmark/benchmark.h> #include <x86intrin.h> #include <cstring> #define ARR_SIZE 10240000 #define VAL 50 static int16_t *getRandArr() { auto res = new int16_t[ARR_SIZE]; for (int i = 0; i < ARR_SIZE; ++i) { res[i] = static_cast<int16_t>(rand() % (VAL * 2)); } return res; } static auto arr = getRandArr(); static int16_t *getAllignedArr() { auto res = aligned_alloc(32, sizeof(int16_t) * ARR_SIZE); memcpy(res, arr, sizeof(int16_t) * ARR_SIZE); return static_cast<int16_t *>(res); } static auto allignedArr = getAllignedArr(); static void BM_Count(benchmark::State &state) { for (auto _ : state) { int64_t cnt = 0; for (int i = 0; i < ARR_SIZE; ++i) if (arr[i] == VAL) ++cnt; benchmark::DoNotOptimize(cnt); } } BENCHMARK(BM_Count); static void BM_SSE_COUNT_SET_EPI(benchmark::State &state) { for (auto _ : state) { int64_t cnt = 0; auto sseVal = _mm_set1_epi16(VAL); for (int i = 0; i < ARR_SIZE; i += 8) { cnt += _popcnt32( _mm_movemask_epi8( _mm_cmpeq_epi16( sseVal, _mm_set_epi16(arr[i + 7], arr[i + 6], arr[i + 5], arr[i + 4], arr[i + 3], arr[i + 2], arr[i + 1], arr[i]) ) ) ); } benchmark::DoNotOptimize(cnt >> 1); } } BENCHMARK(BM_SSE_COUNT_SET_EPI); static void BM_SSE_COUNT_LOADU(benchmark::State &state) { for (auto _ : state) { int64_t cnt = 0; auto sseVal = _mm_set1_epi16(VAL); for (int i = 0; i < ARR_SIZE; i += 8) { cnt += _popcnt32( _mm_movemask_epi8( _mm_cmpeq_epi16( sseVal, _mm_loadu_si128((__m128i *) &arr[i]) ) ) ); } benchmark::DoNotOptimize(cnt >> 1); } } BENCHMARK(BM_SSE_COUNT_LOADU); static void BM_SSE_COUNT_DIRECT(benchmark::State &state) { for (auto _ : state) { int64_t cnt = 0; auto sseVal = _mm_set1_epi16(VAL); for (int i = 0; i < ARR_SIZE; i += 8) { cnt += _popcnt32( _mm_movemask_epi8( _mm_cmpeq_epi16( sseVal, *(__m128i *) &allignedArr[i] ) ) ); } benchmark::DoNotOptimize(cnt >> 1); } } BENCHMARK(BM_SSE_COUNT_DIRECT); #ifdef __AVX2__ static void BM_AVX_COUNT_LOADU(benchmark::State &state) { for (auto _ : state) { int64_t cnt = 0; auto avxVal = _mm256_set1_epi16(VAL); for (int i = 0; i < ARR_SIZE; i += 16) { cnt += _popcnt32( _mm256_movemask_epi8( _mm256_cmpeq_epi16( avxVal, _mm256_loadu_si256((__m256i *) &arr[i]) ) ) ); } benchmark::DoNotOptimize(cnt >> 1); } } BENCHMARK(BM_AVX_COUNT_LOADU); static void BM_AVX_COUNT_LOAD(benchmark::State &state) { for (auto _ : state) { int64_t cnt = 0; auto avxVal = _mm256_set1_epi16(VAL); for (int i = 0; i < ARR_SIZE; i += 16) { cnt += _popcnt32( _mm256_movemask_epi8( _mm256_cmpeq_epi16(avxVal, _mm256_load_si256((__m256i *) &allignedArr[i]) ) ) ); } benchmark::DoNotOptimize(cnt >> 1); } } BENCHMARK(BM_AVX_COUNT_LOAD); static void BM_AVX_COUNT_DIRECT(benchmark::State &state) { for (auto _ : state) { int64_t cnt = 0; auto avxVal = _mm256_set1_epi16(VAL); for (int i = 0; i < ARR_SIZE; i += 16) { cnt += _popcnt32( _mm256_movemask_epi8( _mm256_cmpeq_epi16( avxVal, *(__m256i *) &allignedArr[i] ) ) ); } benchmark::DoNotOptimize(cnt >> 1); } } BENCHMARK(BM_AVX_COUNT_DIRECT); #endif BENCHMARK_MAIN();
تبدو النتائج الجديدة هكذا (-O0):
--------------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------------- BM_Count 17226622 ns 17062958 ns 41 BM_SSE_COUNT_SET_EPI 8901343 ns 8814845 ns 79 BM_SSE_COUNT_LOADU 3664778 ns 3664766 ns 185 BM_SSE_COUNT_DIRECT 3468436 ns 3468423 ns 202 BM_AVX_COUNT_LOADU 2090817 ns 2090796 ns 343 BM_AVX_COUNT_LOAD 1904424 ns 1904419 ns 364 BM_AVX_COUNT_DIRECT 1814875 ns 1814854 ns 385
إجمالي التسارع الكلي هو 9 مرات ، من المتوقع أن يكون AVX أسرع من SSE بمقدار 2 مرات تقريبًا.
هل _mm256_load_si256
من الوصول المباشر للذاكرة؟
لا توجد إجابة محددة. C -O0
أبطأ من الوصول المباشر ، ولكن أسرع من _mm256_loadu_si256
:
--------------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------------- BM_AVX_COUNT_LOADU 2090817 ns 2090796 ns 343 BM_AVX_COUNT_LOAD 1904424 ns 1904419 ns 364 BM_AVX_COUNT_DIRECT 1814875 ns 1814854 ns 385
C -O3
أسرع من الوصول المباشر للذاكرة ، لكن لا يزال بطيئًا _mm256_loadu_si256
.
--------------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------------- BM_AVX_COUNT_LOADU 992319 ns 992368 ns 701 BM_AVX_COUNT_LOAD 956120 ns 956166 ns 712 BM_AVX_COUNT_DIRECT 1027624 ns 1027674 ns 730
في رمز الإنتاج ، لا يزال من الأفضل استخدام _mm256_load_si256
بدلاً من الوصول المباشر ؛ يمكن للمترجم تحسين هذا الخيار بشكل أفضل.
هل يؤثر استخدام أوامر AVX على سجلات SSE التي تؤثر على السرعة؟
الجواب القصير هو لا . بالنسبة للتجربة ، قمت بجمع وتشغيل المعيار مع -mavx2
و -msse4.2
.
-mavx2
_popcnt32(_mm_movemask_epi8(_mm_cmpeq_epi16(...)))
يتحول إلى
vpcmpeqw %xmm1,%xmm0,%xmm0 vpmovmskb %xmm0,%edx popcnt %edx,%edx
النتائج:
------------------------------------------------------------ Benchmark Time CPU Iterations ------------------------------------------------------------ BM_SSE_COUNT_SET_EPI 9376699 ns 9376767 ns 75 BM_SSE_COUNT_LOADU 4425510 ns 4425560 ns 159 BM_SSE_COUNT_DIRECT 3938604 ns 3938648 ns 177
-msse4.2
_popcnt32(_mm_movemask_epi8(_mm_cmpeq_epi16(...)))
يتحول إلى
pcmpeqw %xmm1,%xmm0 pmovmskb %xmm0,%edx popcnt %edx,%edx
النتائج:
------------------------------------------------------------ Benchmark Time CPU Iterations ------------------------------------------------------------ BM_SSE_COUNT_SET_EPI 9309352 ns 9309375 ns 76 BM_SSE_COUNT_LOADU 4382183 ns 4382195 ns 159 BM_SSE_COUNT_DIRECT 3944579 ns 3944590 ns 176
مكافأة
_popcnt32(_mm256_movemask_epi8(_mm256_cmpeq_epi16(...)))
أوامر AVX _popcnt32(_mm256_movemask_epi8(_mm256_cmpeq_epi16(...)))
إلى
vpcmpeqw %ymm1,%ymm0,%ymm0 vpmovmskb %ymm0,%edx popcnt %edx,%edx
هل _popcnt
سيء جدًا؟
في أحد التعليقات كتب أنترفيس :
وحتى الآن ، لديك عيب في الخوارزمية. لماذا تفعل ذلك من خلال movemask + popcnt؟ بالنسبة للصفائف التي لا تزيد عن 2 ^ 18 عنصرًا ، يمكنك أولاً جمع مجموع العناصر:
auto cmp = _mm_cmpeq_epi16 (sseVal ، sseArr) ؛
cmp = _mm_and_si128 (cmp، _mm_set1_epi16 (1)) ؛
sum = _mm_add_epi16 (sum، cmp)؛
ثم ، في نهاية الدورة ، قم بإضافة واحدة أفقية (لا تنسى الفائض).
أنا جعلت معيارا
static void BM_AVX_COUNT_DIRECT_WO_POPCNT(benchmark::State &state) { auto avxVal1 = _mm256_set1_epi16(1); for (auto _ : state) { auto sum = _mm256_set1_epi16(0); auto avxVal = _mm256_set1_epi16(VAL); for (int i = 0; i < ARR_SIZE; i += 16) { sum = _mm256_add_epi16( sum, _mm256_and_si256( avxVal1, _mm256_cmpeq_epi16( avxVal, *(__m256i *) &allignedArr[i]) ) ); } auto arrSum = (uint16_t *) ∑ size_t cnt = 0; for (int j = 0; j < 16; ++j) cnt += arrSum[j]; benchmark::DoNotOptimize(cnt >> 1); } }
واتضح أنها أبطأ من c -O0
:
--------------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------------- BM_AVX_COUNT_DIRECT 1814821 ns 1814785 ns 392 BM_AVX_COUNT_DIRECT_WO_POPCNT 2386289 ns 2386227 ns 287
وأسرع قليلاً مع -O3
:
--------------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------------- BM_AVX_COUNT_DIRECT 960941 ns 960924 ns 722 BM_AVX_COUNT_DIRECT_WO_POPCNT 948611 ns 948596 ns 732