Skip to Content

О компиляторах и процессорах: AVX

Армянское радио Нас спрашивают:

Как измениться производительность intrinsic варианта на Core-i7, если поменять
_mm_dp_ps на _mm256_dp_ps
_mm_blend_ps на _mm_blend256_ps

То-есть насколько вырастить производительность если мы совсем на AVX переедем и будет обрабатывать по 8 float за проход? А то слухи разные ходят... от 0% до 200% роста.

Отвечаем:

Если игнорировать возможную нечетность размера данных, то код получается таким:

  1. ALIGN1(32) float matX2[3][8] ALIGN2(32) = {
  2.         { 0.17f,0.22f,0.33f,0.44f,0.17f,0.22f,0.33f,0.44f},
  3.         {0.55f,0.66f,0.77f,0.88f,0.55f,0.66f,0.77f,0.88f},
  4.         {1.01f,1.02f,1.03f,1.04f,1.01f,1.02f,1.03f,1.04f}
  5. } ;
  6.  
  7. void dotp_avx(float data[], int sz)
  8. {
  9.         int i;
  10.         __m256 m0 = _mm256_load_ps(matX2[0]);
  11.         __m256 m1 = _mm256_load_ps(matX2[1]);
  12.         __m256 m2 = _mm256_load_ps(matX2[2]);
  13.  
  14.         for(i=0;i<sz/2;i++)
  15.         {
  16.                 __m256 d0 = _mm256_load_ps(&data[i*8]);
  17.                 _mm_prefetch((const char*)&data[i*8+16],_MM_HINT_NTA);
  18.                 __m256 r0 = _mm256_dp_ps(d0,m0,0xf1);  
  19.                 __m256 t0 = _mm256_dp_ps(d0,m1,0xff);
  20.                 __m256 t1 = _mm256_dp_ps(d0,m2,0xff);
  21.                 r0 = _mm256_blend_ps(r0,t0,0x22);
  22.                 r0 = _mm256_blend_ps(r0,t1,0x44);
  23.                 _mm256_stream_ps(&data[i*8],r0);       
  24.         }
  25. }
Матрицу на которую множим - пришлось удвоить, естественно. Остальное все прозрачно, про _mm_prefetch напишу ниже, в результатах.

Да, для удобства родились такие вот макросы, используемые выше:

  1. #if defined(_MSC_VER) || defined(__INTEL_COMPILER)
  2. #define ALIGN1(v) __declspec(align(v))
  3. #define ALIGN2(v)
  4. #else /* GCC */
  5. #define ALIGN1(v)
  6. #define ALIGN2(v) __attribute__((aligned(v)))
  7. #endif

Результаты

  • Без _mm_prefetch: 625 Mpix/sec
  • С _mm_prefetch: 637 Mpix/sec
Скорость практически одинакова (разница в пределах разброса отдельных прогонов) для всех компиляторов (Intel 12.0, MSVC 2010 SP1, gcc 4.6.2).

Другими словами, в 1.36 раза быстрее, чем было с SSE4.1 на Intel C++ и в 1.14 раза быстрее, чем для MSVC.

Comments

а разве они уже умеют 1 блоком делать операцию над 256 бит в

а разве они уже умеют 1 блоком делать операцию над 256 бит вектором за раз ?
Вроде ж писали, что регистры по 256, но каждую половинку по факту обрабатывает свой блок.

Ну так а мне, по счастью, и не надо 256 бит за раз, у меня с

Ну так а мне, по счастью, и не надо 256 бит за раз, у меня скалярное умножение двух векторов длиной 4
И _mm256_dp_ps() делает именно это - умножает зараз две половинки.

На моём i7 2670QM (2GHz) этот код выдаёт 475 MPix/sec если д

На моём i7 2670QM (2GHz) этот код выдаёт 475 MPix/sec если данные в L2, 450 MPix/sec если данные в L3, и 310 MPix/sec если данные в памяти

  1. SECTION .rdata
  2. align 32
  3.         table0 dd 0.44, 0.33, 0.22, 0.11, 0.44, 0.33, 0.22, 0.11
  4.         table1 dd 0.88, 0.77, 0.66, 0.55, 0.88, 0.77, 0.66, 0.55
  5.         table2 dd 1.04, 1.03, 1.02, 1.01, 1.04, 1.03, 1.02, 1.01
  6.  
  7. SECTION .text
  8.  
  9. global convert_pixels
  10.  
  11. ; extern "C" void convert_pixels(const float* source, float* destination, size_t length)
  12. convert_pixels:
  13.         ; rcx - source
  14.         ; rdx - destination
  15.         ; r8 - length
  16.  
  17.         vzeroupper
  18.         align 32
  19. .main_processing_loop:
  20.         vmovaps ymm0, [rcx]
  21.         vmovaps ymm1, [rcx + 32]
  22.         vmovaps ymm2, [rcx + 64]
  23.         vmovaps ymm3, [rcx + 96]
  24.  
  25.         vmulps ymm4, ymm0, [table0]
  26.         vmulps ymm5, ymm0, [table1]
  27.         vmulps ymm6, ymm0, [table2]
  28.         vmulps ymm7, ymm1, [table0]
  29.         vmulps ymm8, ymm1, [table1]
  30.         vmulps ymm9, ymm1, [table2]
  31.         vmulps ymm10, ymm2, [table0]
  32.         vmulps ymm11, ymm2, [table1]
  33.         vmulps ymm12, ymm2, [table2]
  34.         vmulps ymm13, ymm3, [table0]
  35.         vmulps ymm14, ymm3, [table1]
  36.         vmulps ymm15, ymm3, [table2]
  37.  
  38.         vhaddps ymm4, ymm4, ymm5
  39.         vhaddps ymm6, ymm6, ymm7
  40.         vhaddps ymm8, ymm8, ymm9
  41.         vhaddps ymm10, ymm10, ymm11
  42.         vhaddps ymm12, ymm12, ymm13
  43.         vhaddps ymm14, ymm14, ymm15
  44.        
  45.         vhaddps ymm4, ymm4, ymm6
  46.         vhaddps ymm8, ymm8, ymm10
  47.         vhaddps ymm12, ymm12, ymm14
  48.        
  49.         vmovaps [rdx], ymm4
  50.         vmovaps [rdx + 32], ymm8
  51.         vmovaps [rdx + 64], ymm12
  52.         sub rcx, -128
  53.         add rdx, 96
  54.         sub r8, 32
  55.         jnz .main_processing_loop
  56.         vzeroupper
  57.  
  58.         ret

Попробуйте погонять вот этот код (компилировать Nasm'ом) <co

Попробуйте погонять вот этот код (компилировать Nasm'ом)

  1. ; Processing speed on Core i7 2630QM (2GHz, Signle-Channel DDR3-1333 (PC3-10700))
  2. ; * Data in memory: 8400 MB/s (300 MPix/s)
  3. ; * Data in L3: 11300 MB/s (400 MPix/s)
  4. ; * Data in L2: 11500 MB/s (410 MPix/s)
  5.  
  6. SECTION .rdata align(32)
  7.         c1c0 dd 0.88, 0.77, 0.66, 0.55, 0.44, 0.33, 0.22, 0.11
  8.         c2c1 dd 1.04, 1.03, 1.02, 1.01, 0.88, 0.77, 0.66, 0.55
  9.         c0c2 dd 0.44, 0.33, 0.22, 0.11, 1.04, 1.03, 1.02, 1.01
  10.  
  11. SECTION .text
  12.  
  13. global convert_pixels
  14.  
  15. ; extern "C" void convert_pixels(const float* source, float* destination, size_t length)
  16. convert_pixels:
  17.         ; rcx - source
  18.         ; rdx - destination
  19.         ; r8 - length
  20.  
  21.         vzeroupper
  22.         align 32
  23. .main_processing_loop:
  24.         prefetchnta [rcx + 1408]
  25.  
  26.         vmovaps ymm1, [rcx]
  27.         vmovaps ymm12, [rcx + 32]
  28.         vmovaps ymm13, [rcx + 64]
  29.         vmovaps ymm11, [rcx + 96]
  30.        
  31.         vperm2f128 ymm2, ymm1,  ymm12, 00100000b
  32.         vperm2f128 ymm3, ymm1,  ymm12, 00100001b
  33.         vperm2f128 ymm4, ymm12, ymm13, 00100000b
  34.         vperm2f128 ymm6, ymm12, ymm13, 00100001b
  35.         vperm2f128 ymm7, ymm12, ymm13, 00110001b
  36.         vperm2f128 ymm8, ymm13, ymm11, 00100001b
  37.         vperm2f128 ymm9, ymm13, ymm11, 00110001b
  38.  
  39.         vmulps ymm0,  ymm1,  [c1c0]
  40.         vmulps ymm1,  ymm1,  [c2c1]
  41.         vmulps ymm2,  ymm2,  [c0c2]
  42.         vmulps ymm3,  ymm3,  [c1c0]
  43.         vmulps ymm4,  ymm4,  [c0c2]
  44.         vmulps ymm5,  ymm6,  [c1c0]
  45.         vmulps ymm6,  ymm6,  [c2c1]
  46.         vmulps ymm7,  ymm7,  [c0c2]
  47.         vmulps ymm8,  ymm8,  [c2c1]
  48.         vmulps ymm9,  ymm9,  [c0c2]
  49.         vmulps ymm10, ymm11, [c1c0]
  50.         vmulps ymm11, ymm11, [c2c1]
  51.  
  52.         vhaddps ymm0,  ymm0,  ymm1
  53.         vhaddps ymm2,  ymm2,  ymm3
  54.         vhaddps ymm4,  ymm4,  ymm5
  55.         vhaddps ymm6,  ymm6,  ymm7
  56.         vhaddps ymm8,  ymm8,  ymm9
  57.         vhaddps ymm10, ymm10, ymm11
  58.        
  59.         vhaddps ymm0,  ymm0,  ymm2
  60.         vhaddps ymm4,  ymm4,  ymm6
  61.         vhaddps ymm8,  ymm8,  ymm10
  62.  
  63.  
  64.         vmovntps [rdx], ymm0
  65.         vmovntps [rdx + 32], ymm4
  66.         vmovntps [rdx + 64], ymm8
  67.         sub rcx, -128
  68.         add rdx, 96
  69.         sub r8, 32
  70.         jnz .main_processing_loop
  71.         vzeroupper
  72.  
  73.         ret

Попробую, но пока не понимаю что он делает, тем паче что пер

Попробую, но пока не понимаю что он делает, тем паче что передача параметров через регистр с инфраструктурой C++ как-то плохо совместима, ну да придумаю что-нибудь.

Calling convention стандартная для Windows x64. В ней первые

Calling convention стандартная для Windows x64. В ней первые четыре параметра передаются в регистрах

Я что про это хочу сказать (пройдя в отладчике и поняв) 1)

Я что про это хочу сказать (пройдя в отладчике и поняв)
1) Идея отличная, работать должно быстро.
Но
2) То что мы 16 входных значений упаковали в 12 выходных - это для каких-то применений хорошо, а для последующей обработки в FP - категорически неудобно. Поэтому в моих реализациях 4-й компонент выхода обнулялся и это было спецально.

3) Конкретно в этом коде кажется есть ошибка (проверил дважды, вроде в _mm я все правильно перенес) от которой в ymm4 на выходе неправильно.

2) Сделать код, который будет выдавать 16 выходных значений

2) Сделать код, который будет выдавать 16 выходных значений гораздо проще, но я оптимизировал код из этого поста, который выдаёт три компоненты
3) Что-то я не вижу ошибки. Комментарии к коду добавил здесь

Не, тот код меняет 3 компоненты, а 4-ю просто оставляет как

Не, тот код меняет 3 компоненты, а 4-ю просто оставляет как есть. В том коде просто нету внешнего цикла, который img+=4
А вот тут он есть, ну да не суть.

Что касается ошибки, то я просто накормил повторяющимися входными данными (1,2,3,4) и ожидал увидеть повторяющиеся выходные, а в ymm4 было другое. Возможно, я просто неаккуратно перенес (хотя проверил).

Хрен с ним, идея что можно горизонтально складывать для скалярного произведения - понятна, а остальное неважно.

Ок, вот <a href="http://pastebin.com/rbpdgEyz">новая версия<

Ок, вот новая версия для 4-х компонентных пискелей на выходе. Теперь банановый ещё быстрее

Ага, пощупаю. А действительно работать в два потока "с нача

Ага, пощупаю.

А действительно работать в два потока "с начала до середины" и "с середины до конца" - быстрее?

На моём ноуте быстрее, хотя ненамного (8700 MB/s vs 8500 MB/

На моём ноуте быстрее, хотя ненамного (8700 MB/s vs 8500 MB/s) и я не уверен, что разница статистически значима. В общем случае это зависит от множества параметров (сколько массивов данных читает/пишет программа, сколько буферов загрузки и записи есть в процессоре, сколько cache misses может одновременно обрабатывать контроллер кэша, сколько открытых DRAM страниц может держать контроллер памяти, и т.д.). Я как раз делаю ресёрч на эту тему

Ого! 1032Mpix/sec (16.5gb/sec) Это не развернутый вариант,

Ого!

1032Mpix/sec (16.5gb/sec)
Это не развернутый вариант, а по 8 значений (один регистр) за раз, in place (пишем откуда читали) и без префетча. Для сравнения с другими.

И не ассемблер, а intrinsics, хотя разницы быть не должно.

Не, я в первый раз неправильно померял (в инварианте цикла о

Не, я в первый раз неправильно померял (в инварианте цикла ошибся).

725Mpix/sec. Быстрее чем все что раньше, но и только.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <s> <i> <b> <blockquote>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Images can be added to this post.

More information about formatting options



.