Я полагаю, что проблема заключается в фактическом количестве имеющихся у вас FPU, как предложено @Aconcagua.
" логические процессоры " aka " гиперпоточность " - это не то же самое, что иметь вдвое больше ядер.
8 ядер в гиперпоточности - это еще 4 "настоящих" ядра. Если вы внимательно посмотрите на время, вы увидите, что время выполнения почти одинаково, пока вы не используете более 4 потоков. Когда вы используете более 4 потоков, вы можете начать исчерпывать FPU.
Однако, чтобы лучше понять проблему, я бы посоветовал взглянуть на фактический код сборка .
Когда мы хотим измерить необработанную производительность, мы должны помнить, что наш код C ++ - это просто представление более высокого уровня , и фактический исполняемый файл может сильно отличаться от того, что мы ожидаем.
Компилятор выполнит свою оптимизацию, ЦП выполнит все не по порядку и т. Д. ...
Поэтому, прежде всего, я бы рекомендовал избегать использования постоянных пределов в ваших циклах. В зависимости от случая, компилятор может развернуть цикл или даже полностью заменить его результатом его вычисления.
Например, код:
int main()
{
int z = 0;
for(int k=0; k < 1000; k++)
z += k;
return z;
}
компилируется GCC 8.1 с оптимизацией -O2 как:
main:
mov eax, 499500
ret
Как видите, петля исчезла !
Компилятор заменил его фактическим конечным результатом.
Использование такого примера для измерения производительности опасно . В приведенном выше примере итерации 1000 или 80000 раз абсолютно одинаковы, поскольку цикл заменяется константой в обоих случаях (конечно, если вы переполните переменную цикла, компилятор не сможет ее заменить).
MSVC не настолько агрессивен, но вы никогда точно не знаете, что делает оптимизатор, если только вы не посмотрите на код сборки .
Проблема с просмотром полученного кода сборки в том, что он может быть массивным ...
Простой способ решить эту проблему - использовать отличный проводник компилятора . Просто введите свой код C / C ++, выберите компилятор, который вы хотите использовать, и посмотрите результат.
Теперь вернемся к вашему коду, я протестировал его с помощью компилятора, используя MSVC2015 для x86_64.
Без оптимизаций код ассемблера выглядит практически одинаково, за исключением встроенного в конце кода для преобразования в double (cvtsi2sd).
Однако все становится интересным, когда мы включаем оптимизацию (это значение по умолчанию при компиляции в режиме выпуска).
Компиляция с флагом -O2 , код сборки, генерируемый, когда mDummy имеет значение long (32 бита), равен:
Algorithm::runAlgorithm, COMDAT PROC
xor r8d, r8d
mov r9d, r8d
npad 10
$LL4@runAlgorit:
mov rax, r9
mov edx, 100000 ; 000186a0H
npad 8
$LL7@runAlgorit:
dec r8
add r8, rax
add rax, -4
sub rdx, 1
jne SHORT $LL7@runAlgorit
add r9, 2
cmp r9, 400000 ; 00061a80H
jl SHORT $LL4@runAlgorit
mov DWORD PTR [rcx], r8d
ret 0
Algorithm::runAlgorithm ENDP
конец, когда mDummy является float :
Algorithm::runAlgorithm, COMDAT PROC
mov QWORD PTR [rsp+8], rbx
mov QWORD PTR [rsp+16], rdi
xor r10d, r10d
xor r8d, r8d
$LL4@runAlgorit:
xor edx, edx
xor r11d, r11d
xor ebx, ebx
mov r9, r8
xor edi, edi
npad 4
$LL7@runAlgorit:
add r11, -3
add r10, r9
mov rax, r8
sub r9, 4
sub rax, rdx
dec rax
add rdi, rax
mov rax, r8
sub rax, rdx
add rax, -2
add rbx, rax
mov rax, r8
sub rax, rdx
add rdx, 4
add r11, rax
cmp rdx, 200000 ; 00030d40H
jl SHORT $LL7@runAlgorit
lea rax, QWORD PTR [r11+rbx]
inc r8
add rax, rdi
add r10, rax
cmp r8, 200000 ; 00030d40H
jl SHORT $LL4@runAlgorit
mov rbx, QWORD PTR [rsp+8]
xorps xmm0, xmm0
mov rdi, QWORD PTR [rsp+16]
cvtsi2ss xmm0, r10
movss DWORD PTR [rcx], xmm0
ret 0
Algorithm::runAlgorithm ENDP
Не вдаваясь в детали того, как работают эти два кода или почему оптимизатор ведет себя по-разному в этих двух случаях, мы ясно видим некоторые различия.
В частности, вторая версия (та, в которой mDummy является float):
- немного длиннее
- использует больше регистров
- чаще обращайтесь к памяти
Таким образом, помимо проблемы с гиперпоточностью, вторая версия с большей вероятностью приведет к ошибкам в кэше, и, поскольку кэш используется совместно, это также может повлиять на конечное время выполнения.
Более того, такие вещи, как турбо-буст могут также появиться При нагрузке ваш процессор может замедляться, что приводит к увеличению общего времени выполнения.
Для записей это то, что производит clang с включенной оптимизацией:
Algorithm::runAlgorithm(): # @Algorithm::runAlgorithm()
mov dword ptr [rdi], 0
ret
Confused? Ну ... никто не использует mDummy в другом месте, поэтому Clang решил полностью удалить все это ...:)