Почему компилятор не оптимизирует число с плавающей точкой * 2 в приращении показателя?

Я часто замечал, что gcc преобразует умножения в сдвиги в исполняемом файле. Нечто подобное может произойти при умножении int и float, Например, 2 * f, может просто увеличить показатель степени f на 1, сохраняя несколько циклов. Сделайте компиляторы, возможно, если кто-то попросит их сделать это (например, через -ffast-math) в общем, так ли?

Достаточно ли умны компиляторы, чтобы сделать это, или мне нужно сделать это самостоятельно, используя scalb*() или же ldexp()/frexp() семейная функция?

46

Решение

Например, 2 * f может просто увеличить показатель f на 1,
сохранение некоторых циклов.

Это просто не правда.

Во-первых, у вас есть слишком много угловых случаев, таких как ноль, бесконечность, Нан и денормали. Тогда у вас есть проблема с производительностью.

Недоразумение заключается в том, что приращение показателя не быстрее, чем умножение.

Если вы посмотрите на инструкции по аппаратному обеспечению, нет прямого способа увеличить показатель степени.
Итак, что вам нужно сделать вместо этого:

  1. Побитовое преобразование в целое число.
  2. Инкремент экспоненты
  3. Побитовое преобразование обратно в число с плавающей точкой.

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

Поэтому причина, по которой компилятор не выполняет эту «оптимизацию», заключается в том, что она не работает быстрее.

81

Другие решения

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

addl $0x100000, 4(%eax)   # x86 asm example

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

20

Распространенные форматы с плавающей точкой, в частности IEEE 754, не хранят показатель степени как простое целое число, и обработка его как целого числа не даст правильных результатов.

В 32-разрядном формате с плавающей запятой или 64-разрядном двойном поле экспоненты равно 8 или 11 битам соответственно. Коды экспонент от 1 до 254 (в формате с плавающей запятой) или от 1 до 2046 (в двойном выражении) действуют как целые числа: если вы добавляете одно к одному из этих значений, а результатом является одно из этих значений, то представленное значение удваивается. Однако добавить один не удается в следующих ситуациях:

  • Начальное значение 0 или субнормальное. В этом случае поле экспоненты начинается с нуля, и добавление одного к нему добавляет 2-126 (в поплавке) или 2-1022 (в два раза) на номер; это не удваивает число.
  • Начальное значение превышает 2127 (в поплавке) или 2+1023 (в два раза). В этом случае поле экспоненты начинается с 254 или 2046, и добавление к нему одного изменяет число на NaN; это не удваивает число.
  • Начальное значение — бесконечность или NaN. В этом случае поле экспоненты начинается с 255 или 2047, и добавление единицы к нему изменяет его на ноль (и, вероятно, переполняется в знаковый бит). Результат равен нулю или субнормальному, но должен быть равен бесконечности или NaN соответственно.

(Выше для положительных знаков. Ситуация симметрична с отрицательными знаками.)

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

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

18

Дело не в том, что компиляторы или авторы компиляторов не умны. Это больше похоже на соблюдение стандартов и создание всех необходимых «побочных эффектов», таких как Infs, Nans и denormals.

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

9

На самом деле, это то, что происходит в оборудовании.

2 также передается в FPU как число с плавающей запятой, с мантиссой 1,0 и показателем степени 2 ^ 1. Для умножения, экспоненты добавляются, а мантиссы умножаются.

Учитывая, что есть специальное оборудование для обработки сложного случая (умножение на значения, которые не являются степенями двух), и специальный случай не обрабатывается ничем не хуже, чем при использовании выделенного оборудования, нет смысла иметь дополнительные схемы и инструкции ,

3

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

0

Предыдущий вопрос Stackoverflow о умножении на степени 2. Консенсус и фактические реализации доказали, что, к сожалению, в настоящее время нет более эффективного способа, чем стандартное умножение.

0

Если вы считаете, что умножение на два означает увеличение показателя на 1, подумайте еще раз. Вот возможные случаи для арифметики IEEE 754 с плавающей точкой:

Случай 1: бесконечность и NaN остаются без изменений.

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

Случай 3: нормализованные числа с плавающей точкой с показателем степени, меньшим максимально возможного показателя, имеют показатель экспоненты, увеличенный на единицу. Yippee !!!

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

Случай 5: Денормализованные числа с плавающей запятой с очищенным старшим битом мантиссы, включая +0 и -0, имеют их мантиссу, сдвинутую влево на одну позицию бита, оставляя показатель степени неизменным.

Я очень сомневаюсь, что компилятор, создающий целочисленный код, правильно обрабатывающий все эти случаи, будет где-нибудь так же быстро, как встроенная в процессор с плавающей точкой. И это подходит только для умножения на 2,0. Для умножения на 4,0 или 0,5 применяется совершенно новый набор правил. А в случае умножения на 2.0 вы можете попробовать заменить x * 2.0 на x + x, и многие компиляторы делают это. То есть они делают это, потому что процессор мог бы, например, сделать одно сложение и одно умножение одновременно, но не по одному для каждого вида. Так иногда вы бы предпочли х * 2,0, а иногда х + х, в зависимости от того, что другие операции нужно выполнять одновременно.

0