В моем приложении есть ядро с множественным добавлением, и я хочу повысить его производительность.
Я использую Intel Core i7-960 (тактовая частота 3,2 ГГц) и уже вручную внедрил ядро с использованием встроенных функций SSE следующим образом:
for(int i=0; i<iterations; i+=4) {
y1 = _mm_set_ss(output[i]);
y2 = _mm_set_ss(output[i+1]);
y3 = _mm_set_ss(output[i+2]);
y4 = _mm_set_ss(output[i+3]);
for(k=0; k<ksize; k++){
for(l=0; l<ksize; l++){
w = _mm_set_ss(weight[i+k+l]);
x1 = _mm_set_ss(input[i+k+l]);
y1 = _mm_add_ss(y1,_mm_mul_ss(w,x1));
…
x4 = _mm_set_ss(input[i+k+l+3]);
y4 = _mm_add_ss(y4,_mm_mul_ss(w,x4));
}
}
_mm_store_ss(&output[i],y1);
_mm_store_ss(&output[i+1],y2);
_mm_store_ss(&output[i+2],y3);
_mm_store_ss(&output[i+3],y4);
}
Я знаю, что могу использовать упакованные векторы fp для повышения производительности, и я уже сделал это успешно. , но я хочу знать, почему один скалярный код не соответствует пиковой производительности процессора.
Производительность этого ядра на моей машине составляет ~1,6 операций FP за цикл, в то время как максимум может быть 2 операции FP за цикл (поскольку FP add + FP mul могут выполняться параллельно).
Если я прав из изучения сгенерированного ассемблерного кода, идеальное расписание будет выглядеть следующим образом, где инструкция mov
занимает 3 такта, задержка переключения из домена нагрузки в домен FP для зависимые инструкции занимают 2 такта, умножение FP занимает 4 цикла, а сложение FP занимает 3 цикла. (Обратите внимание, что зависимость от умножения -> добавить не приводит к задержке переключения, поскольку операции принадлежат одному и тому же домену).
Согласно измеренной производительности (~80% от максимальной теоретической производительности) накладные расходы составляют ~3 инструкции на 8 циклов.
Я пытаюсь либо:
Конечно, существует проблема с промахами кеша и несовпадением данных, которые могут увеличить задержку перемещения инструкции, но есть ли какие-то другие факторы, которые могли бы сыграть здесь роль? Например, киоски чтения регистров или что-то в этом роде?
Надеюсь, моя проблема ясна, заранее спасибо за ответы!
Обновление: сборка внутреннего цикла выглядит следующим образом:
...
Block 21:
movssl (%rsi,%rdi,4), %xmm4
movssl (%rcx,%rdi,4), %xmm0
movssl 0x4(%rcx,%rdi,4), %xmm1
movssl 0x8(%rcx,%rdi,4), %xmm2
movssl 0xc(%rcx,%rdi,4), %xmm3
inc %rdi
mulss %xmm4, %xmm0
cmp $0x32, %rdi
mulss %xmm4, %xmm1
mulss %xmm4, %xmm2
mulss %xmm3, %xmm4
addss %xmm0, %xmm5
addss %xmm1, %xmm6
addss %xmm2, %xmm7
addss %xmm4, %xmm8
jl 0x401b52 <Block 21>
...