Иногда пытаться «оптимизировать» код C ++, добавляя циклы для проверки времени, в общем, довольно глупо, и это один из таких случаев: (
Ваш код В буквальном смысле сводится только к этому:
int main()
{
TimeStamp start = Clock::now();
TimeStamp end = Clock::now();
double dt = chrono::duration_cast<chrono::nanoseconds>(end-start).count();
cout<<dt<<endl;
return 0;
}
Компилятор не глуп, и поэтому он решил удалить ваш внутренний цикл (поскольку вывод не используется, и поэтому цикл является избыточным).
Даже если компилятор решил сохранить цикл, вы даете 3 инструкции по памяти для каждого дополнения. Если ваша оперативная память составляет 1600 МГц, а ваш процессор 3200 МГц, то ваши тесты просто доказывают, что у вас ограниченная пропускная способность памяти. Подобные профилирующие циклы бесполезны, вам всегда будет лучше тестировать реальную ситуацию в профилировщике ....
Во всяком случае, вернемся к рассматриваемому циклу. Давайте добавим код в проводник компилятора и поиграем с некоторыми опциями ...
https://godbolt.org/z/5SJQHb
F0 : Просто простая, скучная С-петля.
for(int i = 0 ; i < MAX ; i++)
{
out[i] = in1[i] + in2[i];
}
Компилятор выводит этот внутренний цикл:
vmovups ymm0,YMMWORD PTR [rsi+r8*4]
vmovups ymm1,YMMWORD PTR [rsi+r8*4+0x20]
vmovups ymm2,YMMWORD PTR [rsi+r8*4+0x40]
vmovups ymm3,YMMWORD PTR [rsi+r8*4+0x60]
vaddps ymm0,ymm0,YMMWORD PTR [rdx+r8*4]
vaddps ymm1,ymm1,YMMWORD PTR [rdx+r8*4+0x20]
vaddps ymm2,ymm2,YMMWORD PTR [rdx+r8*4+0x40]
vaddps ymm3,ymm3,YMMWORD PTR [rdx+r8*4+0x60]
vmovups YMMWORD PTR [rdi+r8*4],ymm0
vmovups YMMWORD PTR [rdi+r8*4+0x20],ymm1
vmovups YMMWORD PTR [rdi+r8*4+0x40],ymm2
vmovups YMMWORD PTR [rdi+r8*4+0x60],ymm3
Развернуто, имеет дело с 32xfloats за итерацию (в AVX2) [+ дополнительный код для обработки до 31 элемента в конце итерации]
F1 : ваш «оптимизированный» цикл SSE выше. (Очевидно, этот код не обрабатывает до 3 элементов в конце цикла)
for(int i = 0 ; i < MAX ; i+=4)
{
__m128 a = _mm_load_ps(&in1[i]);
__m128 b = _mm_load_ps(&in2[i]);
__m128 result = _mm_add_ps(a,b);
_mm_store_ps(&out[i],result);
}
Это выводит:
vmovaps xmm0,XMMWORD PTR [rsi+rcx*4]
vaddps xmm0,xmm0,XMMWORD PTR [rdx+rcx*4]
vmovaps XMMWORD PTR [rdi+rcx*4],xmm0
vmovaps xmm0,XMMWORD PTR [rsi+rcx*4+0x10]
vaddps xmm0,xmm0,XMMWORD PTR [rdx+rcx*4+0x10]
vmovaps XMMWORD PTR [rdi+rcx*4+0x10],xmm0
vmovaps xmm0,XMMWORD PTR [rsi+rcx*4+0x20]
vaddps xmm0,xmm0,XMMWORD PTR [rdx+rcx*4+0x20]
vmovaps XMMWORD PTR [rdi+rcx*4+0x20],xmm0
vmovaps xmm0,XMMWORD PTR [rsi+rcx*4+0x30]
vaddps xmm0,xmm0,XMMWORD PTR [rdx+rcx*4+0x30]
vmovaps XMMWORD PTR [rdi+rcx*4+0x30],xmm0
Таким образом, компилятор развернул цикл, но он вернулся к SSE (как было запрошено), поэтому теперь он вдвое меньше производительности исходного цикла (не совсем верно - пропускная способность памяти будет ограничивающим фактором) .
F2 : ваш развернутый цикл C ++ (с исправленными индексами, но все еще не может обработать последние 3 элемента)
for(int i = 0 ; i < MAX ; i += 4)
{
out[i + 0] = in1[i + 0] + in2[i + 0];
out[i + 1] = in1[i + 1] + in2[i + 1];
out[i + 2] = in1[i + 2] + in2[i + 2];
out[i + 3] = in1[i + 3] + in2[i + 3];
}
И вывод:
vmovss xmm0,DWORD PTR [rsi+rax*4]
vaddss xmm0,xmm0,DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4],xmm0
vmovss xmm0,DWORD PTR [rsi+rax*4+0x4]
vaddss xmm0,xmm0,DWORD PTR [rdx+rax*4+0x4]
vmovss DWORD PTR [rdi+rax*4+0x4],xmm0
vmovss xmm0,DWORD PTR [rsi+rax*4+0x8]
vaddss xmm0,xmm0,DWORD PTR [rdx+rax*4+0x8]
vmovss DWORD PTR [rdi+rax*4+0x8],xmm0
vmovss xmm0,DWORD PTR [rsi+rax*4+0xc]
vaddss xmm0,xmm0,DWORD PTR [rdx+rax*4+0xc]
vmovss DWORD PTR [rdi+rax*4+0xc],xmm0
Ну, это полностью не удалось векторизовать! Это просто обработка 1 сложения за раз. Ну, это обычно сводится к псевдониму указателя, поэтому я изменю прототип функции из этого:
void func(float* out, const float* in1, const float* in2, int MAX);
к этому: ( F4 )
void func(
float* __restrict out,
const float* __restrict in1,
const float* __restrict in2,
int MAX);
и теперь компилятор выведет что-то векторизованное:
vmovups xmm0,XMMWORD PTR [rsi+rcx*4]
vaddps xmm0,xmm0,XMMWORD PTR [rdx+rcx*4]
vmovups xmm1,XMMWORD PTR [rsi+rcx*4+0x10]
vaddps xmm1,xmm1,XMMWORD PTR [rdx+rcx*4+0x10]
vmovups XMMWORD PTR [rdi+rcx*4],xmm0
vmovups xmm0,XMMWORD PTR [rsi+rcx*4+0x20]
vaddps xmm0,xmm0,XMMWORD PTR [rdx+rcx*4+0x20]
vmovups XMMWORD PTR [rdi+rcx*4+0x10],xmm1
vmovups xmm1,XMMWORD PTR [rsi+rcx*4+0x30]
vaddps xmm1,xmm1,XMMWORD PTR [rdx+rcx*4+0x30]
vmovups XMMWORD PTR [rdi+rcx*4+0x20],xmm0
vmovups XMMWORD PTR [rdi+rcx*4+0x30],xmm1
ОДНАКО этот код по-прежнему вдвое меньше производительности первой версии ....