Какова относительная скорость сложения с плавающей запятой и умножения с плавающей запятой? - PullRequest
28 голосов
/ 18 июля 2009

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

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

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

Ответы [ 6 ]

22 голосов
/ 18 июля 2009

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

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

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-битной процедуре Если я заменю цикл на

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

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

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

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

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*/

(в основном мулс, но одно дополнение: 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
18 голосов
/ 18 июля 2009

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

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

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

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

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

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

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

7 голосов
/ 18 июля 2009

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

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

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

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

Edit:

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

1 голос
/ 18 июля 2009

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

1 голос
/ 18 июля 2009

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

0 голосов
/ 18 июля 2009

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

...