Подтвердить что ты не робот

То, что относительная скорость добавления плавающей запятой против умножения с плавающей запятой

Десять лет назад было полезно написать числовой код, чтобы избежать использования умножений и делений и вместо этого использовать сложение и вычитание. Хорошим примером является использование передовых различий для оценки полиномиальной кривой вместо вычисления полинома напрямую.

Это все еще так, или современные архитектуры компьютеров продвинулись до точки, где *,/уже не во много раз медленнее, чем +, -?

Чтобы быть конкретным, меня интересует компилированный код C/С++, работающий на современных типичных чипах x86 с обширным бортовым оборудованием с плавающей запятой, а не небольшим микропроцессором, который пытается сделать FP в программном обеспечении. Я понимаю, что конвейерная обработка и другие архитектурные усовершенствования исключают определенные количества циклов, но я все равно хотел бы получить полезную интуицию.

4b9b3361

Ответ 1

Это также зависит от сочетания команд. Ваш процессор будет иметь несколько вычислительных устройств, стоящих в любой момент, и вы получите максимальную пропускную способность, если все они будут заполнены все время. Таким образом, выполнение цикла mul выполняется так же быстро, как выполнение цикла или добавляет - но то же самое не выполняется, если выражение становится более сложным.

Например, возьмите этот цикл:

for(int j=0;j<NUMITER;j++) {
  for(int i=1;i<NUMEL;i++) {
    bla += 2.1 + arr1[i] + arr2[i] + arr3[i] + arr4[i] ;
  }
}

для NUMITER = 10 ^ 7, NUMEL = 10 ^ 2, оба массива инициализированы с небольшими положительными номерами (NaN намного медленнее), это занимает 6,0 секунды, используя удвоения в 64-битном proc. Если я заменил цикл на

bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;

Это займет всего 1,7 секунды... так что, поскольку мы "перегрузили" дополнения, muls были по существу свободными; и сокращение добавок помогло. Это становится более запутанным:

bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;

- то же распределение mul/add, но теперь константа добавляется, а не умножается - занимает 3,7 секунды. Вероятно, ваш процессор оптимизирован для выполнения типичных численных вычислений более эффективно; поэтому dot-продукт, как суммы muls и масштабированных сумм, примерно так же хорош, как и получается; добавление констант не так распространено, так что медленнее...

bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/

снова занимает 1,7 секунды.

bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/

(то же, что и исходный цикл, но без дорогостоящего постоянного добавления: 2,1 секунды)

bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/

(в основном muls, но одно дополнение: 1,9 секунды)

Итак, в основном; сложно сказать, что быстрее, но если вы хотите избежать узких мест, более важно иметь разумную смесь, избегать NaN или INF, избегать добавления констант. Независимо от того, что вы делаете, убедитесь, что вы тестируете и проверяете различные настройки компилятора, так как часто небольшие изменения могут просто сделать разницу.

Еще несколько случаев:

bla *= someval; // someval very near 1.0; takes 2.1 seconds
bla *= arr1[i] ;// arr1[i] all very near 1.0; takes 66(!) seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; // 1.6 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, 2.2 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, floats 2.2 seconds
bla += someval * arr1[i]* arr2[i];// 0.9 in x64, 1.6 in x86
bla += someval * arr1[i];// 0.55 in x64, 0.8 in x86
bla += arr1[i] * arr2[i];// 0.8 in x64, 0.8 in x86, 0.95 in CLR+x64, 0.8 in CLR+x86

Ответ 2

В теории информация здесь:

Справочное руководство по оптимизации архитектуры Intel®64 и IA-32, ПРИЛОЖЕНИЕ C ИНСТРУКЦИЯ ПО ЛИТЕРАТУРЕ И ЧЕРЕЗ ПРОГРАММА

Для каждого процессора, который они перечисляют, латентность на FMUL очень близка к задержке FADD или FDIV. На некоторых более старых процессорах FDIV на 2-3 раза медленнее, чем на более старых процессорах, то же, что и у FMUL.

Предостережения:

  • В документе, на котором я ссылаюсь, фактически говорится, что вы не можете полагаться на эти цифры в реальной жизни, так как процессор будет делать то, что он хочет сделать быстрее, если он исправит.

  • Есть хорошая вероятность, что ваш компилятор решит использовать один из множества новых наборов инструкций, которые имеют размножение/разделение с плавающей запятой.

  • Это сложный документ, предназначенный только для чтения авторами компилятора, и я, возможно, ошибся. Как я не понимаю, почему номер задержки FDIV полностью отсутствует для некоторых процессоров.

Ответ 3

Лучший способ ответить на этот вопрос - фактически написать контрольный показатель/профиль обработки, который вам нужно сделать. Эмпирическое следует использовать по теоретическому, когда это возможно. Особенно, когда это легко достичь.

Если вы уже знаете разные реализации математики, которые вам нужно сделать, вы можете написать несколько разных передач кода математики и посмотреть, где ваши пики производительности. Это позволит процессору/компилятору генерировать различные потоки выполнения для заполнения процессорных конвейеров и дать вам конкретный ответ на ваш ответ.

Если вы заинтересованы в конкретной производительности инструкций типа DIV/MUL/ADD/SUB, вы можете даже бросить в какую-то встроенную сборку, чтобы специально контролировать, какие варианты этой команды выполняются. Однако вам нужно убедиться, что вы выполняете многопроцессорные исполнительные устройства, чтобы получить представление о производительности, на которую способна система.

Кроме того, что-то подобное позволит вам сравнить производительность нескольких вариантов процессора, просто запустив на них одну и ту же программу, а также позволит вам учитывать различия в материнской плате.

Edit:

Базовая архитектура a + - идентична. Таким образом, они логически занимают одинаковое время для вычисления. * С другой стороны, требуется несколько уровней, обычно построенных из "полных сумматоров" для завершения одной операции. Это гарантирует, что в то время, когда * может быть выдан на конвейер каждый цикл, он будет иметь более высокую задержку, чем схема добавления/вычитания. Операция fp/обычно реализуется с использованием метода аппроксимации, который итеративно сходится к правильному ответу с течением времени. Эти типы приближений обычно реализуются посредством умножения. Таким образом, для плавающей точки вы можете предположить, что деление займет больше времени, потому что нецелесообразно "развернуть" умножения (которые уже являются большой схемой внутри и на себе) в конвейер множества схем умножителей. Тем не менее производительность данной системы лучше всего измеряется с помощью тестирования.

Ответ 4

Я не могу найти окончательную ссылку, но обширное экспериментирование говорит мне, что умножение поплавка в настоящее время составляет примерно такую ​​же скорость, что и сложение и вычитание, тогда как деление не (но не "много раз" медленнее). Вы можете получить интуицию, которую хотите, только запустив свои собственные эксперименты - не забудьте заранее создать случайные числа (миллионы), прочитать их перед началом отсчета времени и использовать счетчики производительности процессора (без какого-либо другого процесса, как так как вы можете их остановить) для точного измерения!

Ответ 5

Разница в скорости */vs + - зависит от архитектуры процессора. В целом и с x86, в частности, с современными процессорами разница в скорости стала меньше. * должен быть близок к +, если у вас есть сомнения: просто экспериментируйте. Если у вас действительно тяжелая проблема с большим количеством операций FP, подумайте также об использовании своего GPU (GeForce,...), который работает как векторный процессор.

Ответ 6

Вероятно, очень мало различий во времени между умножением и добавлением. разделение, с другой стороны, все еще значительно медленнее, чем умножение из-за его рекурсивного характера. на современной архитектуре x86 sse-инструкции должны учитываться при выполнении операций с плавающей запятой, а не с использованием fpu. Хотя хороший компилятор C/С++ должен дать вам возможность использовать sse вместо fpu.