Например, будет ли 5 * 5 выполняться быстрее, чем 5000 * 5000?
Для констант времени компиляции 5 * x
дешевле, чем 5000 * x
, потому что первое можно сделатьс lea eax, [rdi + rdi*4]
.
Но для переменных времени выполнения единственной целочисленной инструкцией с зависимой от данных производительностью является деление. Это применимо к любому основному ЦП: конвейерная обработка настолько важна, что даже в некоторых случаяхмогут работать с меньшей задержкой, обычно они этого не делают, потому что это усложняет планирование.(Нельзя, чтобы один и тот же исполнительный модуль выдавал 2 результата в одном и том же цикле; вместо этого ЦП просто хочет знать, что ввод входных данных в один цикл обязательно приведет к тому, что ответ выйдет через 3 цикла.)
(Для FP опять-таки только деление и sqrt имеют производительность, зависящую от данных на обычных процессорах.)
Код с использованием целых чисел или FP, который имеет любое зависящее от данных ветвление, может быть намного медленнее, если ветви идут другим путем.(например, предсказание ветвления «обучается» на одной последовательности переходов для двоичного поиска; поиск с другим ключом будет медленнее, потому что он будет неверно предсказан хотя бы один раз.)
И для записи, предложения по использованию Math.Pow
вместо целого числа *
безумны.Простое преобразование целого числа в double
и обратно медленнее, чем умножение самого себя на умножение на целое число.
Ответ Адама связывает эталон, который зацикливается на большом массиве, с возможностью автоматической векторизации.SSE / AVX2 имеет только 32-битное целочисленное умножение.И 64-битная занимает больше пропускной способности памяти.Именно поэтому он показывает ускорения для 16 и 8-битных целых чисел.Таким образом, он обнаруживает, что c=a*b
работает на половинной скорости на процессоре Haswell, но это не применимо к случаю цикла.
В скалярном коде imul r64, r64
имеет производительность, идентичную imul r32, r32
на основных процессорах Intel (начиная с по крайней мере Nehalem) и на Ryzen (https://agner.org/optimize/). Обе: 1 мегапиксель, 3 такта, пропускная способность 1 / такт.
Это только семейство AMD Bulldozer и AMDAtom и Silvermont, где 64-разрядное скалярное умножение медленнее (конечно, в 64-разрядном режиме! В 32-разрядном режиме работа с 64-разрядными целыми числами медленнее.)
Оптимизация цикла
Для фиксированного значения number
вместо пересчета i*number
компиляторы могут и оптимизируют его до inum += number
. Это называется оптимизация снижения прочности , потому что сложение является «более слабой» (немного более дешевой) операцией, чем умножение.
for(...) {
var value = i * number
var valuePow2 = value * value;
}
может быть скомпилировано в asm, что-то вроде
var value = 0;
for(...) {
var valuePow2 = value * value;
...
value += number;
}
Вы можете попробовать написатьтаким образом, на случай, если компилятор этого не сделаетили вы.
Но целочисленное умножение очень дешево и особенно конвейерно на современных процессорах, особенно.Он имеет немного большую задержку, чем add, и может работать на меньшем количестве портов (обычно только 1 на тактовую пропускную способность вместо 4 для add), но вы говорите, что выполняете значительную работу с valuePow2
.Это должно позволить внеочередному выполнению скрыть задержку.
Если вы проверяете asm и компилятор использует отдельный счетчик цикла, увеличивающийся на 1, вы также можете попытаться удержать компилятор вручнуюв оптимизации цикла для использования value
в качестве счетчика цикла.
var maxval = number * 10000;
for (var value = 0; i <= maxval; value += number) {
var valuePow2 = value * value;
...
}
Будьте осторожны, если number*10000
может переполниться, если вам нужно правильно обернуть.В этом случае этот цикл будет выполнять гораздо меньше итераций.(Если number
не будет настолько большим, что value += number
также закутывается ...)