Я пытаюсь векторизовать некоторые простые вычисления для ускорения от архитектуры SIMD. Тем не менее, я также хочу поместить их как встроенные функции, потому что вызовы функций и не векторизованные коды также занимают время вычислений. Тем не менее, я не всегда могу достичь их одновременно. Фактически, большинство моих встроенных функций не могут быть автоматически векторизованы. Вот простой тестовый код, который работает:
inline void add1(double *v, int Length) {
for(int i=0; i < Length; i++) v[i] += 1;
}
void call_add1(double v[], int L) {
add1(v, L);
}
int main(){return 0;}
В Mac OS X 10.12.3 скомпилируйте его:
clang++ -O3 -Rpass=loop-vectorize -Rpass-analysis=loop-vectorize -std=c++11 -ffast-math test.cpp
test.cpp:2:5: remark: vectorized loop (vectorization width: 2, interleaved count: 2) [-Rpass=loop-vectorize]
for(int i=0; i < Length; i++) v[i] += 1;
^
Однако что-то очень похожее (только перемещение аргументов в call_add1) не работает:
inline void add1(double *v, int Length) {
for(int i=0; i < Length; i++) v[i] += 1;
}
void call_add1() {
double v[20]={0,1,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9};
int L=20;
add1(v, L);
}
int main(){ return 0;}
Компиляция с помощью той же команды не приводит к выводу. Почему это происходит? Как я могу сделать так, чтобы циклы во встроенных функциях всегда автоматически векторизовались? Я хочу векторизовать множество функциональных циклов, поэтому надеюсь, что исправление не будет сложным.
Компиляция вашего кода с -fsave-optimization-record
показывает, что цикл был развернут, а затем устранен.
--- !Passed
Pass: loop-unroll
Name: FullyUnrolled
DebugLoc: { File: main.cpp, Line: 2, Column: 5 }
Function: _Z9call_add1v
Args:
- String: 'completely unrolled loop with '
- UnrollCount: '20'
- String: ' iterations'
...
--- !Passed
Pass: gvn
Name: LoadElim
DebugLoc: { File: main.cpp, Line: 2, Column: 40 }
Function: _Z9call_add1v
Args:
- String: 'load of type '
- Type: double
- String: ' eliminated'
- String: ' in favor of '
- InfavorOfValue: '0.000000e+00'
Если вы поместите 4000 элементов в массив, он превысит порог оптимизатора, и clang включит векторизацию.
Это потому, что для второго случая компилятор знает, что никаких побочных эффектов нет, и оптимизирует все https://godbolt.org/g/CnojEi лязг 4.0.0 с -O3
оставляет только:
call_add1():
rep ret
main:
xor eax, eax
ret
И ты не понимаешь про магию петель.
В 1-м случае компилятор создает некоторое тело для функции, потому что функция изменяет аргумент. Если вы скомпилировали это как объектный файл. Вы можете ссылаться на эту функцию, и она будет работать. Я предполагаю, что если параметры будут постоянными, то, возможно, функция также останется с пустым телом.
Когда вы распечатываете содержимое, программы не идентичны, но обе они используют векторизованные инструкции: https://godbolt.org/g/KF1kNt
Похоже, что компилятор просто развернет и оптимизирует цикл, когда v
указано явно. Который является хорошая вещь: код, который не должен быть выполнен, является самым быстрым.
Чтобы убедиться, что это оптимизация, вы можете попытаться сделать некоторые переменные изменчивыми (живой пример).