2016-01-06 8 views
3

Я использую Haswell Core i7-4790K.Компилятор Intel C использует несимметричные перемещения SIMD с выровненной памятью

Когда я компилирую следующий пример игрушки с icc -O3 -std=c99 -march=core-avx2 -g:

#include <stdio.h> 
#include <stdint.h> 
#include <immintrin.h> 

typedef struct { 
    __m256i a; 
    __m256i b; 
    __m256i c; 
} mystruct_t; 

#define SIZE  1000 
#define TEST_VAL 42 

int _do(mystruct_t* array) { 
    int value = 0; 

    for (size_t i = 0; i < SIZE; ++i) { 
    array[i].a = _mm256_set1_epi8(TEST_VAL + i*3 ); 
    array[i].b = _mm256_set1_epi8(TEST_VAL + i*3 + 1); 
    array[i].c = _mm256_set1_epi8(TEST_VAL + i*3 + 2); 

    value += _mm_popcnt_u32(_mm256_movemask_epi8(array[i].a)) + 
      _mm_popcnt_u32(_mm256_movemask_epi8(array[i].b)) + 
      _mm_popcnt_u32(_mm256_movemask_epi8(array[i].c)); 
    } 

    return value; 
} 

int main() { 
    mystruct_t* array = (mystruct_t*)_mm_malloc(SIZE * sizeof(*array), 32); 
    printf("%d\n", _do(array)); 
    _mm_free(array); 
} 

Следующий код ASM производится для функции _do():

0x0000000000400bc0 <+0>: xor %eax,%eax 
0x0000000000400bc2 <+2>: xor %ecx,%ecx 
0x0000000000400bc4 <+4>: xor %edx,%edx 
0x0000000000400bc6 <+6>: nopl (%rax) 
0x0000000000400bc9 <+9>: nopl 0x0(%rax) 
0x0000000000400bd0 <+16>: lea  0x2b(%rdx),%r8d 
0x0000000000400bd4 <+20>: inc %ecx 
0x0000000000400bd6 <+22>: lea  0x2a(%rdx),%esi 
0x0000000000400bd9 <+25>: lea  0x2c(%rdx),%r9d 
0x0000000000400bdd <+29>: add $0x3,%edx 
0x0000000000400be0 <+32>: vmovd %r8d,%xmm1 
0x0000000000400be5 <+37>: vpbroadcastb %xmm1,%ymm4 
0x0000000000400bea <+42>: vmovd %esi,%xmm0 
0x0000000000400bee <+46>: vpmovmskb %ymm4,%r11d 
0x0000000000400bf2 <+50>: vmovd %r9d,%xmm2 
0x0000000000400bf7 <+55>: vmovdqu %ymm4,0x20(%rdi) 
0x0000000000400bfc <+60>: vpbroadcastb %xmm0,%ymm3 
0x0000000000400c01 <+65>: vpbroadcastb %xmm2,%ymm5 
0x0000000000400c06 <+70>: vpmovmskb %ymm3,%r10d 
0x0000000000400c0a <+74>: vmovdqu %ymm3,(%rdi) 
0x0000000000400c0e <+78>: vmovdqu %ymm5,0x40(%rdi) 
0x0000000000400c13 <+83>: popcnt %r11d,%esi 
0x0000000000400c18 <+88>: add $0x60,%rdi 
0x0000000000400c1c <+92>: vpmovmskb %ymm5,%r11d 
0x0000000000400c20 <+96>: popcnt %r10d,%r9d 
0x0000000000400c25 <+101>: popcnt %r11d,%r8d 
0x0000000000400c2a <+106>: add %esi,%r9d 
0x0000000000400c2d <+109>: add %r8d,%r9d 
0x0000000000400c30 <+112>: add %r9d,%eax 
0x0000000000400c33 <+115>: cmp $0x3e8,%ecx 
0x0000000000400c39 <+121>: jb  0x400bd0 <_do+16> 
0x0000000000400c3b <+123>: vzeroupper 
0x0000000000400c3e <+126>: retq 
0x0000000000400c3f <+127>: nop 

Если я компилирую тот же код, используя gcc-5 -O3 -std=c99 -mavx2 -march=native -g, следующий Код ASM для функции _do():

0x0000000000400650 <+0>: lea  0x17700(%rdi),%r9 
0x0000000000400657 <+7>: mov $0x2a,%r8d 
0x000000000040065d <+13>: xor %eax,%eax 
0x000000000040065f <+15>: nop 
0x0000000000400660 <+16>: lea  0x1(%r8),%edx 
0x0000000000400664 <+20>: vmovd %r8d,%xmm2 
0x0000000000400669 <+25>: xor %esi,%esi 
0x000000000040066b <+27>: vpbroadcastb %xmm2,%ymm2 
0x0000000000400670 <+32>: vmovd %edx,%xmm1 
0x0000000000400674 <+36>: add $0x60,%rdi 
0x0000000000400678 <+40>: lea  0x2(%r8),%edx 
0x000000000040067c <+44>: vpbroadcastb %xmm1,%ymm1 
0x0000000000400681 <+49>: vmovdqa %ymm2,-0x60(%rdi) 
0x0000000000400686 <+54>: add $0x3,%r8d 
0x000000000040068a <+58>: vmovd %edx,%xmm0 
0x000000000040068e <+62>: vpmovmskb %ymm2,%edx 
0x0000000000400692 <+66>: vmovdqa %ymm1,-0x40(%rdi) 
0x0000000000400697 <+71>: vpbroadcastb %xmm0,%ymm0 
0x000000000040069c <+76>: popcnt %edx,%esi 
0x00000000004006a0 <+80>: vpmovmskb %ymm1,%edx 
0x00000000004006a4 <+84>: popcnt %edx,%edx 
0x00000000004006a8 <+88>: vpmovmskb %ymm0,%ecx 
0x00000000004006ac <+92>: add %esi,%edx 
0x00000000004006ae <+94>: vmovdqa %ymm0,-0x20(%rdi) 
0x00000000004006b3 <+99>: popcnt %ecx,%ecx 
0x00000000004006b7 <+103>: add %ecx,%edx 
0x00000000004006b9 <+105>: add %edx,%eax 
0x00000000004006bb <+107>: cmp %rdi,%r9 
0x00000000004006be <+110>: jne  0x400660 <_do+16> 
0x00000000004006c0 <+112>: vzeroupper 
0x00000000004006c3 <+115>: retq 

Мои вопросы:

1) Почему icc использует неустановленные ходы (vmovdqu) в отличие от gcc?

2) Есть ли штраф, когда vmovdqu используется вместо vmovdqa на выровненную память?

P.S: Проблема та же, что и инструкции/регистры SSE.

Благодаря

+2

ICC начал делать это в 2012 году, а MSVC последовал примеру через год. Досада заключается в том, что она * не * сбой, когда данные смещены. Поэтому вы даже не знаете, что проблема с производительностью. К счастью, инструкции потоковой передачи имеют только согласованные версии. Таким образом, компилятор не может «обмануть». – Mysticial

ответ

5

Там нет никакого штрафа к использованию VMOVDQU, когда адрес выровнен. В этом случае поведение идентично использованию VMOVDQA.

Что касается «почему», может быть, не может быть однозначного ответа. Это возможно, что ICC делает это преднамеренно, чтобы пользователи, которые позже звонили _do с несогласованным аргументом, не сбой, но также возможно, что это просто возникающее поведение компилятора. Кто-то из команды компилятора Intel мог ответить на этот вопрос, остальные из нас могут только догадываться.

+0

Спасибо! Я полностью понимаю, что поведение компилятора с закрытым исходным кодом непредсказуемо. Но так как нет никакого штрафа за использование 'vmovdqu', когда адрес выровнен, не очень важно понять, почему он используется. Мне было довольно интересно, что что-то не так с выравниванием памяти в моем коде. – benlaug

+1

@benlaug: FWIW, я смутно помню разговор с инженером ICC, который, возможно, сказал, что они просто используют невыровненные нагрузки всякий раз, когда вы явно не запрашиваете выравнивание через intrinsics, но моя память об этом довольно расплывчата. –

+0

Странно, что если я заменил 'array [i] .a = _mm256_set1_epi8 (TEST_VAL + i * 3); ... 'by _mm256_store_si256 (& (массив [i] .a), _mm256_set1_epi8 (TEST_VAL + i * 3)); ... 'icc все еще использует' vmovdqu'. – benlaug

2

Есть три фактора в игре, которые решают большую проблему:

а) разломообразование поведения может быть полезно для отладки производительности, но не так хорошо для производства коды - особенно, когда смесь библиотек 3 участника участвуют - очень мало кто потерпел бы крах при немного более медленной работе своего программного продукта на сайте заказчика

b) Микроархитектура Intel разрешила «невыровненные» формы инструкций по проблеме согласованной производительности данных, начиная с Nehalem, они имеют одинаковую производительность, как «выровненные» формы , AMD сделала это еще до этого, я думаю

с) AVX + улучшили архитектурное поведение нагрузки + OP форм над SSE не-разломообразованием, так

VADDPS ymm0, ymm0, ymmword ptr [rax]; // no longer faults when rax is misaligned 

Поскольку для AVX + мы хотим, чтобы компилятор еще иметь свободу использовать либо автономную или Load + OP формы команд при генерации кода из встроенных функций, для кода, как это:

_mm256_add_ps(a, *(__m256*)data_ptr ); 

с AVX + компилятор может использовать vMOVUs (VMOVUPS/VMOVUPD/VMOVDQU) для всех нагрузок и поддерживать равномерное поведение под нагрузкой + OP форм

Это необходимо, когда исходный код изменяется незначительно или генерируется код того же самого кода (например,между различными компиляторами/версиями или из-за вложения) и переключатели генерации кода из инструкции Load + OP в автономные команды Load и OP, поведение нагрузки такое же, как и с Load + OP, то есть без сбоев.

Таким образом, AVX с описанной выше практикой компилятора и использованием форматов инструкций «без выровненных» форм позволяет единообразное поведение без ошибок для кода SIMD без потери производительности при выровненных данных.

Конечно, все еще существуют (относительно редкие) цели использования ориентированных инструкций для невременных хранилищ (vMOVNTDQ/vMOVNTPS/vMOVNTPD) и загрузки из типов памяти WC (vMOVNDQA), которые поддерживают поведение сбоев для несогласованных адресов.

-Max Locktyukhin, Intel

+0

AMD K10 имеет дешевые неуравновешенные грузы, но не магазины. то есть 'movdqu xmm, [mem]' равно 2 за такт с выровненными адресами, но 'movdqu [mem], xmm' магазины - один на 2 такта, против одного на 1 такт для' movdqa'. (http://agner.org/optimize/) –

+0

И повторно: решение о качестве реализации для использования невыровненных нагрузок, даже когда компилятор считает, что согласованные нагрузки будут безопасными: gcc принимает противоположное решение и использует 'vmovdqa' всякий раз достаточные гарантии выравнивания известны во время компиляции (даже если программист дал обещания, которые оказались ложными, используя '_mm_store' вместо' storeu'). Это полезно для отладки при планировании выравнивания данных, но в противном случае это не так. Ваше рассуждение о том, почему имеет смысл всегда использовать неприсоединенную форму, имеет большой смысл. –

 Смежные вопросы

  • Нет связанных вопросов^_^