Почему MSVS не оптимизирует прочь +0?
Этот вопрос демонстрирует очень интересное явление: денормализованные числа с плавающей точкой замедляют код более чем на порядок.
Поведение хорошо объясняется в принятом ответе. Тем не менее, есть один комментарий с 153 отзывами, на который я не могу найти удовлетворительный ответ:
Почему в этом случае компилятор просто не удаляет + / - 0?!? - Майкл Дорган
Примечание: у меня сложилось впечатление, что 0f является/должно быть точно представимым (более того - это двоичное представление должно быть всеми нулями), но я не могу найти такое утверждение в стандарте c11.Цитата, доказывающая это, или аргумент, опровергающий это утверждение, было бы очень желательно.Несмотря на это, Майкл вопрос является основным вопросом здесь.
§5.2.4.2.2
Реализация может дать ноль и значения, которые не являются числами с плавающей точкой (например, бесконечности и NaN), знак или могут оставить их без знака.
Ответы
Ответ 1
Компилятор не может устранить добавление положительного нуля с плавающей запятой, потому что это не операция идентификации. По правилам IEEE 754 результат добавления +0. до -0. не -0; это +0.
Компилятор может исключить вычитание +0. или добавлением -0. потому что это операции с идентификаторами.
Например, когда я скомпилирую это:
double foo(double x) { return x + 0.; }
с Apple GNU C 4.2.1 с использованием -O3
на Intel Mac, итоговый код сборки содержит addsd LC0(%rip), %xmm0
. Когда я скомпилирую это:
double foo(double x) { return x - 0.; }
нет инструкции добавления; сборка просто возвращает свой вход.
Итак, скорее всего, код в исходном вопросе содержал инструкцию add для этого оператора:
y[i] = y[i] + 0;
но не содержит инструкции для этого оператора:
y[i] = y[i] - 0;
Однако первый оператор включал арифметику с субнормальными значениями в y[i]
, поэтому было достаточно замедлить работу программы.
Ответ 2
Это не нулевая постоянная 0.0f
которая денормализована, это значения, которые приближаются к нулю на каждой итерации цикла. По мере того как они становятся ближе и ближе к нулю, им нужно больше точности для представления, следовательно, денормализация. В первоначальном вопросе это значения y[i]
.
Принципиальное различие между медленной и быстрой версиями кода заключается в выражении y[i] = y[i] + 0.1f;
, Как только эта строка выполняется, дополнительная точность в плавающей точке теряется, и денормализация, необходимая для представления этой точности, больше не нужна. После этого операции с плавающей точкой на y[i]
остаются быстрыми, потому что они не денормализованы.
Почему лишняя точность теряется при добавлении 0.1f
? Потому что числа с плавающей запятой имеют только столько значащих цифр. Скажем, у вас достаточно места для хранения трех значащих цифр, затем 0.00001 = 1e-5
и 0.00001 + 0.1 = 0.1
, по крайней мере, для этого примера формата с плавающей запятой, поскольку в нем нет места для хранения 0.10001
бита в 0.10001
.