Эта попытка микробенчмарка слишком наивна почти во всех возможных случаях, чтобы вы могли получить какие-либо значимые результаты.
Даже если вы исправили поверхностные проблемы (поэтому код не оптимизировался)прочь), существуют серьезные проблемы, прежде чем вы сможете сделать вывод о том, когда ваш asm
будет лучше, чем *
.
(Подсказка: , вероятно, никогда . Компиляторы уже знают, какОптимальное умножение целых чисел и понимание семантики этой операции. Принудительное использование imul
вместо автоматической векторизации или других оптимизаций приведет к потерям.)
Оба рассчитанырегионы пусты, потому что оба умножения могут оптимизировать прочь .(asm
- это не asm volatile
, и вы не используете результат.) Вы измеряете только шум и / или увеличение частоты процессора до максимального значения turbo до clock()
.
И даже если бы этого не было, одна imul
инструкция в основном неизмерима с функцией с такими дополнительными издержками, как clock()
.Возможно, если вы сериализовали с lfence
, чтобы заставить процессор ждать imul
для выхода из системы, прежде чем rdtsc
... См. RDTSCP в NASM всегда возвращает одно и то же значение
ИлиВы скомпилировали с отключенной оптимизацией, что бессмысленно.
Вы не можете измерить оператор C *
и встроенный asm без некоторого контекста, включающего цикл .И тогда это будет для этого контекста , в зависимости от того, какие оптимизации вы победили, используя встроенный asm.(И что если вы сделаете все, чтобы компилятор не оптимизировал работу для чистой версии C).
Измерение только одного числа для одной инструкции x86 мало о чем вам говорит.Вам необходимо измерить задержку, пропускную способность и стоимость переднего плана, чтобы правильно охарактеризовать ее стоимость.Современные x86-процессоры являются суперскалярными конвейерами с неупорядоченным порядком, поэтому сумма затрат на 2 инструкции зависит от того, зависят ли они друг от друга, и от другого окружающего контекста. Сколько циклов ЦП необходимо для каждой инструкции по сборке?
Автономные определения функций идентичны после вашего изменения, чтобы позволить компиляторувыберите регистры, и ваш asm может встраиваться несколько эффективнее, но это все еще не помогает оптимизации.gcc знает, что 5 * 4 = 20 во время компиляции, поэтому, если бы вы использовали результат, multiply(4,5)
мог бы оптимизироваться до немедленного 20
.Но gcc не знает, что делает asm, поэтому ему просто нужно ввести входные данные хотя бы один раз.(не volatile
означает, что он может CSE результат, если вы использовали asmMultiply(4,5)
в цикле.)
Таким образом, среди прочего, inline asm предотвращает постоянное распространение.Это имеет значение, даже если только один из входных данных является константой, а другой - переменной времени выполнения.Многие малые целочисленные умножители могут быть реализованы с помощью одной или двух инструкций LEA или сдвига (с меньшей задержкой, чем 3c для imul
на современном x86).
https://gcc.gnu.org/wiki/DontUseInlineAsm
Единственный вариант использования, который я мог бы себе представить asm
, помогает, если компилятор использовал 2x инструкции LEA в ситуации, которая на самом деле связана с внешним интерфейсом, где imul $constant, %[src], %[dst]
позволил бы копировать и умножать с 1 моп вместо 2. Новаш asm исключает возможность использования немедленных (вы только допустили ограничения регистра), а встроенный GNU C не может позволить вам использовать другой шаблон для непосредственного сравнения с регистром.Может быть, если вы использовали множественные альтернативные ограничения и соответствующее ограничение регистра для части только для регистра?Но нет, вам все равно нужно иметь что-то вроде asm("%2, %1, %0" :...)
, и это не может работать для рег, рег.
. Вы можете использовать if(__builtin_constant_p(a)) { asm using imul-immediate } else { return a*b; }
, который будет работать с GCC, чтобы позволить вам победить LEA.Или, в любом случае, просто потребуйте постоянного множителя, так как вы захотите использовать его только для конкретной версии gcc, чтобы обойти конкретную пропущенную оптимизацию.( то есть это настолько ниша, что на практике вы никогда бы этого не сделали. )
Ваш код в проводнике компилятора Godbolt , с clang7.0 -O3
для x86-64 Соглашения о вызовах System V:
# clang7.0 -O3 (The functions both inline and optimize away)
main: # @main
push rbx
sub rsp, 16
call clock
mov rbx, rax # save the return value
call clock
sub rax, rbx # end - start time
cvtsi2sd xmm0, rax
divsd xmm0, qword ptr [rip + .LCPI2_0]
movsd qword ptr [rsp + 8], xmm0 # 8-byte Spill
call clock
mov rbx, rax
call clock
sub rax, rbx # same block again for the 2nd group.
xorps xmm0, xmm0
cvtsi2sd xmm0, rax
divsd xmm0, qword ptr [rip + .LCPI2_0]
movsd qword ptr [rsp], xmm0 # 8-byte Spill
mov edi, offset .L.str
mov al, 1
movsd xmm0, qword ptr [rsp + 8] # 8-byte Reload
call printf
mov edi, offset .L.str.1
mov al, 1
movsd xmm0, qword ptr [rsp] # 8-byte Reload
call printf
xor eax, eax
add rsp, 16
pop rbx
ret
TL: DR: если вы хотите понять производительность встроенного asm на этом детализированном уровне детализации, вам необходимо понять, как компиляторы оптимизируют в первую очередь.