О компиляторах и процессорах: AVX
lexa - 04/Сен/2011 19:22
Армянское радио Нас спрашивают:
Как измениться производительность intrinsic варианта на Core-i7, если поменять
_mm_dp_ps на _mm256_dp_ps
_mm_blend_ps на _mm_blend256_psТо-есть насколько вырастить производительность если мы совсем на AVX переедем и будет обрабатывать по 8 float за проход? А то слухи разные ходят... от 0% до 200% роста.
Отвечаем:
Если игнорировать возможную нечетность размера данных, то код получается таким:
ALIGN1(32) float matX2[3][8] ALIGN2(32) = {
{ 0.17f,0.22f,0.33f,0.44f,0.17f,0.22f,0.33f,0.44f},
{0.55f,0.66f,0.77f,0.88f,0.55f,0.66f,0.77f,0.88f},
{1.01f,1.02f,1.03f,1.04f,1.01f,1.02f,1.03f,1.04f}
} ;
void dotp_avx(float data[], int sz)
{
int i;
__m256 m0 = _mm256_load_ps(matX2[0]);
__m256 m1 = _mm256_load_ps(matX2[1]);
__m256 m2 = _mm256_load_ps(matX2[2]);
for(i=0;i<sz/2;i++)
{
__m256 d0 = _mm256_load_ps(&data[i*8]);
_mm_prefetch((const char*)&data[i*8+16],_MM_HINT_NTA);
__m256 r0 = _mm256_dp_ps(d0,m0,0xf1);
__m256 t0 = _mm256_dp_ps(d0,m1,0xff);
__m256 t1 = _mm256_dp_ps(d0,m2,0xff);
r0 = _mm256_blend_ps(r0,t0,0x22);
r0 = _mm256_blend_ps(r0,t1,0x44);
_mm256_stream_ps(&data[i*8],r0);
}
}
{ 0.17f,0.22f,0.33f,0.44f,0.17f,0.22f,0.33f,0.44f},
{0.55f,0.66f,0.77f,0.88f,0.55f,0.66f,0.77f,0.88f},
{1.01f,1.02f,1.03f,1.04f,1.01f,1.02f,1.03f,1.04f}
} ;
void dotp_avx(float data[], int sz)
{
int i;
__m256 m0 = _mm256_load_ps(matX2[0]);
__m256 m1 = _mm256_load_ps(matX2[1]);
__m256 m2 = _mm256_load_ps(matX2[2]);
for(i=0;i<sz/2;i++)
{
__m256 d0 = _mm256_load_ps(&data[i*8]);
_mm_prefetch((const char*)&data[i*8+16],_MM_HINT_NTA);
__m256 r0 = _mm256_dp_ps(d0,m0,0xf1);
__m256 t0 = _mm256_dp_ps(d0,m1,0xff);
__m256 t1 = _mm256_dp_ps(d0,m2,0xff);
r0 = _mm256_blend_ps(r0,t0,0x22);
r0 = _mm256_blend_ps(r0,t1,0x44);
_mm256_stream_ps(&data[i*8],r0);
}
}
Да, для удобства родились такие вот макросы, используемые выше:
#if defined(_MSC_VER) || defined(__INTEL_COMPILER)
#define ALIGN1(v) __declspec(align(v))
#define ALIGN2(v)
#else /* GCC */
#define ALIGN1(v)
#define ALIGN2(v) __attribute__((aligned(v)))
#endif
#define ALIGN1(v) __declspec(align(v))
#define ALIGN2(v)
#else /* GCC */
#define ALIGN1(v)
#define ALIGN2(v) __attribute__((aligned(v)))
#endif
Результаты
- Без _mm_prefetch: 625 Mpix/sec
- С _mm_prefetch: 637 Mpix/sec
Другими словами, в 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 если данные в памяти
SECTION .rdata
align 32
table0 dd 0.44, 0.33, 0.22, 0.11, 0.44, 0.33, 0.22, 0.11
table1 dd 0.88, 0.77, 0.66, 0.55, 0.88, 0.77, 0.66, 0.55
table2 dd 1.04, 1.03, 1.02, 1.01, 1.04, 1.03, 1.02, 1.01
SECTION .text
global convert_pixels
; extern "C" void convert_pixels(const float* source, float* destination, size_t length)
convert_pixels:
; rcx - source
; rdx - destination
; r8 - length
vzeroupper
align 32
.main_processing_loop:
vmovaps ymm0, [rcx]
vmovaps ymm1, [rcx + 32]
vmovaps ymm2, [rcx + 64]
vmovaps ymm3, [rcx + 96]
vmulps ymm4, ymm0, [table0]
vmulps ymm5, ymm0, [table1]
vmulps ymm6, ymm0, [table2]
vmulps ymm7, ymm1, [table0]
vmulps ymm8, ymm1, [table1]
vmulps ymm9, ymm1, [table2]
vmulps ymm10, ymm2, [table0]
vmulps ymm11, ymm2, [table1]
vmulps ymm12, ymm2, [table2]
vmulps ymm13, ymm3, [table0]
vmulps ymm14, ymm3, [table1]
vmulps ymm15, ymm3, [table2]
vhaddps ymm4, ymm4, ymm5
vhaddps ymm6, ymm6, ymm7
vhaddps ymm8, ymm8, ymm9
vhaddps ymm10, ymm10, ymm11
vhaddps ymm12, ymm12, ymm13
vhaddps ymm14, ymm14, ymm15
vhaddps ymm4, ymm4, ymm6
vhaddps ymm8, ymm8, ymm10
vhaddps ymm12, ymm12, ymm14
vmovaps [rdx], ymm4
vmovaps [rdx + 32], ymm8
vmovaps [rdx + 64], ymm12
sub rcx, -128
add rdx, 96
sub r8, 32
jnz .main_processing_loop
vzeroupper
ret
Попробуйте погонять вот этот код (компилировать Nasm'ом) <co
Попробуйте погонять вот этот код (компилировать Nasm'ом)
; Processing speed on Core i7 2630QM (2GHz, Signle-Channel DDR3-1333 (PC3-10700))
; * Data in memory: 8400 MB/s (300 MPix/s)
; * Data in L3: 11300 MB/s (400 MPix/s)
; * Data in L2: 11500 MB/s (410 MPix/s)
SECTION .rdata align(32)
c1c0 dd 0.88, 0.77, 0.66, 0.55, 0.44, 0.33, 0.22, 0.11
c2c1 dd 1.04, 1.03, 1.02, 1.01, 0.88, 0.77, 0.66, 0.55
c0c2 dd 0.44, 0.33, 0.22, 0.11, 1.04, 1.03, 1.02, 1.01
SECTION .text
global convert_pixels
; extern "C" void convert_pixels(const float* source, float* destination, size_t length)
convert_pixels:
; rcx - source
; rdx - destination
; r8 - length
vzeroupper
align 32
.main_processing_loop:
prefetchnta [rcx + 1408]
vmovaps ymm1, [rcx]
vmovaps ymm12, [rcx + 32]
vmovaps ymm13, [rcx + 64]
vmovaps ymm11, [rcx + 96]
vperm2f128 ymm2, ymm1, ymm12, 00100000b
vperm2f128 ymm3, ymm1, ymm12, 00100001b
vperm2f128 ymm4, ymm12, ymm13, 00100000b
vperm2f128 ymm6, ymm12, ymm13, 00100001b
vperm2f128 ymm7, ymm12, ymm13, 00110001b
vperm2f128 ymm8, ymm13, ymm11, 00100001b
vperm2f128 ymm9, ymm13, ymm11, 00110001b
vmulps ymm0, ymm1, [c1c0]
vmulps ymm1, ymm1, [c2c1]
vmulps ymm2, ymm2, [c0c2]
vmulps ymm3, ymm3, [c1c0]
vmulps ymm4, ymm4, [c0c2]
vmulps ymm5, ymm6, [c1c0]
vmulps ymm6, ymm6, [c2c1]
vmulps ymm7, ymm7, [c0c2]
vmulps ymm8, ymm8, [c2c1]
vmulps ymm9, ymm9, [c0c2]
vmulps ymm10, ymm11, [c1c0]
vmulps ymm11, ymm11, [c2c1]
vhaddps ymm0, ymm0, ymm1
vhaddps ymm2, ymm2, ymm3
vhaddps ymm4, ymm4, ymm5
vhaddps ymm6, ymm6, ymm7
vhaddps ymm8, ymm8, ymm9
vhaddps ymm10, ymm10, ymm11
vhaddps ymm0, ymm0, ymm2
vhaddps ymm4, ymm4, ymm6
vhaddps ymm8, ymm8, ymm10
vmovntps [rdx], ymm0
vmovntps [rdx + 32], ymm4
vmovntps [rdx + 64], ymm8
sub rcx, -128
add rdx, 96
sub r8, 32
jnz .main_processing_loop
vzeroupper
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. Быстрее чем все что раньше, но и только.