Почему компилятор не оптимизирует значение с плавающей запятой * 2 в инкремент экспоненты? - программирование
Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

  • Побитовое преобразование в целое.
  • Увеличение экспоненты.
  • Побитовое преобразование обратно в плавающую точку.

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 8

Если вы думаете, что умножение на два означает увеличение показателя на 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, и многие компиляторы сделают это. То есть они делают это, потому что процессор может, например, выполнять одно добавление и одно умножение одновременно, но не один из каждого типа. Поэтому иногда вы предпочитаете x * 2.0, а иногда и x + x, в зависимости от того, какие другие операции нужно делать одновременно.