2016-12-06 12 views
0

У меня есть идея для стиля кода для написания конкретных видов числовых алгоритмов, где вы пишете свой алгоритм исключительно в агностическом макете данных.Можно ли использовать Clang или GCC для автоматического разворачивания вручную развернутых циклов?

т. Е. Все ваши функции принимают (один или несколько) скалярные аргументы и возвращают (через указатель) одно или несколько скалярных возвращаемых значений. Так, например, если у вас есть функция, которая принимает 3D-вектор float, вместо того, чтобы брать структуру с тремя членами или float [3] xyz, вы берете float x, float y, float z.

Идея состоит в том, что вы можете изменить компоновку своих входных и выходных данных, то есть вы можете играть со структурой массива по сравнению с массивом структуры данных структуры, разбитыми макетами для эффективности кеша, SIMD и многоядерной детализацией и т. Д. .. БЕЗ необходимости переписывать весь ваш код для всех комбинаций макетов данных.

стратегия имеет некоторые очевидные недостатки:

  • Вы не можете использовать для петель внутри ваших функций, чтобы сделать код более компактным
  • Ваших функциям нужны больше параметров в их подписях

... но они приемлемы, если ваши массивы коротки, и это избавляет вас от необходимости переписывать свой код несколько раз, чтобы сделать его быстрым.

Но, в частности, я обеспокоен тем, что компиляторы могут не использовать такие вещи, как x + = a; у + = Ь; г + = с; w + = d и autovectorize в единый вектор SIMD add, в случае, если вы хотите сделать SIMD в нижней части стека вызовов, а не делать SIMD в верхней части стека встроенных функций.

Являются ли clang и/или gcc «повторно рулон» вручную развернутыми циклами в C и/или C++-коде (возможно, после того, как функции встроены) и генерируют векторизованный машинный код?

+0

Это невозможно в C. Выберите один язык. И весь вопрос слишком широк. Мы не являемся дискуссионным сайтом. – Olaf

+0

Привет, Олаф, ваш ответ «нет» вполне правдоподобен и нет необходимости в четырех экземплярах того же вопроса. –

+0

Итак, вы намеренно разместили дубликат? – Olaf

ответ

-1

Ваша забота верна. Никакой компилятор не собирается автоматизировать эти 4 добавления. Это просто не стоит, учитывая, что входы не смежны и не выровнены. Стоимость сбора аргументов в регистр SIMD намного выше, чем сохранение векторного сложения.

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

+0

Привет! Идея состоит в том, что вы выставляете эти скалярные аргументы линейно вне функции. Если функция встраивается, компилятор имеет макет данных и то же определение того, что происходит со значениями, но не выражается в цикле for. Я работаю над более конкретным примером. –

+0

В эти дни регистры SIMD ~ часто используются, даже если это всего лишь одна операция с плавающей запятой. –

+0

Компиляторы могут и могут векторизовать одну операцию векторной ширины, если смежные указатели были переданы, и функция была встроена, поэтому компилятор знал это. В особенности это хорошо. (Я могу выкопать пример, если вы хотите). –

1

Я написал код, чтобы сделать тривиальный тест моей идеи:

// Compile using gcc -O4 main.c && objdump -d a.out 

void add4(float x0, float x1, float x2, float x3, 
      float y0, float y1, float y2, float y3, 
      float* out0, float* out1, float* out2, float* out3) { 
    // Non-inlined version of this uses xmm registers and four separate 
    // SIMD operations 
    *out0 = x0 + y0; 
    *out1 = x1 + y1; 
    *out2 = x2 + y2; 
    *out3 = x3 + y3; 
} 
void sub4(float x0, float x1, float x2, float x3, 
      float y0, float y1, float y2, float y3, 
      float* out0, float* out1, float* out2, float* out3) { 
    *out0 = x0 - y0; 
    *out1 = x1 - y1; 
    *out2 = x2 - y2; 
    *out3 = x3 - y3; 
} 
void add4_then_sub4(float x0, float x1, float x2, float x3, 
      float y0, float y1, float y2, float y3, 
      float z0, float z1, float z2, float z3, 
      float* out0, float* out1, float* out2, float* out3) { 
    // In non-inlined version of this, add4 and sub4 get inlined. 
    // xmm regiesters get re-used for the add and subtract, 
    // but there is still no 4-way SIMD 
    float temp0,temp1,temp2,temp3; 
    // temp= x + y 
    add4(x0,x1,x2,x3, 
     y0,y1,y2,y3, 
     &temp0,&temp1,&temp2,&temp3); 
    // out = temp - z 
    sub4(temp0,temp1,temp2,temp3, 
     z0,z1,z2,z3, 
     out0,out1,out2,out3); 
} 
void add4_then_sub4_arrays(const float x[4], 
           const float y[4], 
           const float z[4], 
           float out[4]) 
{ 
    // This is a stand-in for the main function below, but since the arrays are aguments, 
    // they can't be optimized out of the non-inlined version of this function. 
    // THIS version DOES compile into (I think) a bunch of non-aligned moves, 
    // and a single vectorized add a single vectorized subtract 
    add4_then_sub4(x[0],x[1],x[2],x[3], 
      y[0],y[1],y[2],y[3], 
      z[0],z[1],z[2],z[3], 
      &out[0],&out[1],&out[2],&out[3] 
      ); 
} 

int main(int argc, char **argv) 
{ 
} 

Рассмотрим сгенерированную сборку для add4_then_sub4_arrays:

0000000000400600 <add4_then_sub4_arrays>: 
    400600:  0f 57 c0    xorps %xmm0,%xmm0 
    400603:  0f 57 c9    xorps %xmm1,%xmm1 
    400606:  0f 12 06    movlps (%rsi),%xmm0 
    400609:  0f 12 0f    movlps (%rdi),%xmm1 
    40060c:  0f 16 46 08    movhps 0x8(%rsi),%xmm0 
    400610:  0f 16 4f 08    movhps 0x8(%rdi),%xmm1 
    400614:  0f 58 c1    addps %xmm1,%xmm0 
    400617:  0f 57 c9    xorps %xmm1,%xmm1 
    40061a:  0f 12 0a    movlps (%rdx),%xmm1 
    40061d:  0f 16 4a 08    movhps 0x8(%rdx),%xmm1 
    400621:  0f 5c c1    subps %xmm1,%xmm0 
    400624:  0f 13 01    movlps %xmm0,(%rcx) 
    400627:  0f 17 41 08    movhps %xmm0,0x8(%rcx) 
    40062b:  c3      retq 
    40062c:  0f 1f 40 00    nopl 0x0(%rax) 

Массивы не выровнены, так что есть много больше опций перемещения, чем идеальный, и я не уверен, что делает этот xor там, но есть действительно один 4-позиционный add и один 4-way вычитание по желанию.

Таким образом, ответ заключается в том, что gcc имеет как минимум ~ некоторую способность упаковывать скалярные операции с плавающей запятой обратно в операции SIMD.

Update: Ужесточение код с обоими gcc-4.8 -O3 -march=native main.c && objdump -d a.out:

0000000000400600 <add4_then_sub4_arrays>: 
    400600:  c5 f8 10 0e    vmovups (%rsi),%xmm1 
    400604:  c5 f8 10 07    vmovups (%rdi),%xmm0 
    400608:  c5 f0 58 c0    vaddps %xmm0,%xmm1,%xmm0 
    40060c:  c5 f8 10 0a    vmovups (%rdx),%xmm1 
    400610:  c5 f8 5c c1    vsubps %xmm1,%xmm0,%xmm0 
    400614:  c5 f8 11 01    vmovups %xmm0,(%rcx) 
    400618:  c3      retq 
    400619:  0f 1f 80 00 00 00 00 nopl 0x0(%rax) 

и clang-4.0 -O3 -march=native main.c && llvm-objdump -d a.out:

add4_then_sub4_arrays: 
    4005e0:  c5 f8 10 07          vmovups (%rdi), %xmm0 
    4005e4:  c5 f8 58 06          vaddps (%rsi), %xmm0, %xmm0 
    4005e8:  c5 f8 5c 02          vsubps (%rdx), %xmm0, %xmm0 
    4005ec:  c5 f8 11 01          vmovups %xmm0, (%rcx) 
    4005f0:  c3            ret 
    4005f1:  66 66 66 66 66 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax) 
+0

'xor' используется для того, чтобы разбить зависимость, которая в противном случае' movlps' имела бы на старом значении – harold

+0

xorps + movlps - альтернатива braindead для 'movsd (% rdx),% xmm1'. И затем за ним следуют movhps из смежных байтов? Какого черта? Какой компилятор вы использовали, с какими настройками? Очевидно, что «movups (% rdx),% xmm1' будет более эффективным, особенно на любом промежуточном процессоре на полпути. Выполнение несвязанной нагрузки в двух половинах было разумной стратегией для некоторых довольно старых процессоров. –

+0

, поддерживаемый для тестирования и показывающий, что эта реализация вашей идеи не является жизнеспособной с помощью компилятора + параметров, с которыми вы тестировали. От 3x до 4x количество команд для данных источника памяти смешно. (И все эти пары movlps + movhps будут узким местом в порту тасования, так как они являются инструкциями load + blend. См. Http://agner.org/optimize/ для таблиц инструкций и [x86 tag wiki] (http://stackoverflow.com/tags/x86/info)). –