Чому Clang оптимізує пробіг x * 1.0, а НЕ x + 0.0?


125

Чому Clang оптимізує цикл у цьому коді

#include <time.h>
#include <stdio.h>

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] *= 1.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

але не цикл у цьому коді?

#include <time.h>
#include <stdio.h>

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] += 0.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

(Позначення як C і C ++, тому що я хотів би знати, чи відповідь для кожного відрізняється.)


2
Які прапори оптимізації зараз активні?
Iwillnotexist Idonotexist

1
@IwillnotexistIdonotexist: Я просто використовував -O3, я не знаю, як перевірити, що це активує, хоча.
користувач541686

2
Було б цікаво подивитися, що станеться, якщо ви додасте -ffast-math до командного рядка.
підключення

static double arr[N]не дозволено в С; constзмінні не вважаються постійними виразами на цій мові
MM

1
[Вставте примхливий коментар про те, як C не є C ++, навіть якщо ви його вже зателефонували.]
користувач253751

Відповіді:


164

Стандарт IEEE 754-2008 для арифметики з плаваючою комою та стандарт незалежної арифметики (LIA) ISO / IEC 10967, частина 1 відповідають, чому це так.

IEEE 754 § 6.3 Біт знака

Якщо або вхід, або результат - NaN, цей стандарт не інтерпретує ознаку NaN. Однак зауважте, що операції над бітовими рядками - copy, negate, abs, copySign - задають біт знаку результату NaN, інколи заснований на біті знаків операнду NaN. На логічний предикат totalOrder також впливає біт знаків операнду NaN. Для всіх інших операцій цей стандарт не вказує біт знаків результату NaN, навіть коли є лише один вхідний NaN або коли NaN виробляється з недійсної операції.

Коли ані вхідні дані, ані результат не є NaN, знак продукту чи коефіцієнта є виключним АБО знаками операндів; знак суми або різниці x - y, що розглядається як сума x + (−y), відрізняється щонайменше від одного з ознак додавання; а знак результату перетворень, операція квантування, операція roundTo-Integral і roundToIntegralExact (див. 5.3.1) є знаком першого або єдиного операнда. Ці правила застосовуються навіть тоді, коли операнди або результати дорівнюють нулю або нескінченності.

Коли сума двох операндів з протилежними знаками (або різниця двох операндів з подібними знаками) дорівнює рівно нулю, знак цієї суми (або різниці) повинен бути +0 у всіх атрибутах напрямку округлення, крім roundTowardNegative; під цим атрибутом знак точної нульової суми (або різниці) має бути −0. Однак x + x = x - (−x) зберігає той самий знак, що і x, навіть якщо x дорівнює нулю.

Справа доповнення

У режимі округлення за замовчуванням (Round-to-Near, Ties-to Even) ми бачимо, що x+0.0виробляє x, EXCEPT, коли xє -0.0: У цьому випадку ми маємо суму двох операндів з протилежними знаками, сума яких дорівнює нулю, а пункт 6.3 3 правила виробляє це додаток +0.0.

Оскільки +0.0НЕ побітовий ідентичний оригіналу -0.0, і що -0.0є законним значенням , яке може статися в якості вхідних даних, компілятор зобов'язаний помістити в коді , який перетворює потенційні негативні нулі +0.0.

Підсумок: У режимі округлення за замовчуванням, в x+0.0, якщоx

  • не є -0.0 , то xсаме по собі є прийнятним вихідним значенням.
  • є -0.0 , тоді вихідне значення повинно бути таким +0.0 , яке не є бітовим ідентичним -0.0.

Справа множення

У режимі округлення за замовчуванням такої проблеми не виникає x*1.0. Якщо x:

  • - це (під) нормальне число, x*1.0 == xзавжди.
  • є +/- infinity, тоді результат має +/- infinityтой самий знак.
  • є NaN, то відповідно до

    IEEE 754 § 6.2.3 Розмноження NaN

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

    що означає , що показник і мантиса (хоча і це не знак) NaN*1.0є рекомендується , щоб бути незмінними від входу NaN. Знак не визначений відповідно до пункту 6.3p1 вище, але реалізація може вказати, що він є ідентичним джерелу NaN.

  • є +/- 0.0, тоді результат - це 0зі своїм бітовим знаком XOR з бітом знака 1.0, відповідно до §6.3p2. Оскільки біт знака 1.0є 0, вихідне значення не змінюється від вхідного. Таким чином, x*1.0 == xнавіть коли xдорівнює (негативний) нуль.

Випадок віднімання

У режимі округлення за замовчуванням віднімання x-0.0також є неоперативним, оскільки воно еквівалентно x + (-0.0). Якщо xє

  • є NaN, тоді §6.3p1 і §6.2.3 застосовуються приблизно так само, як і для додавання та множення.
  • є +/- infinity, тоді результат має +/- infinityтой самий знак.
  • - це (під) нормальне число, x-0.0 == xзавжди.
  • є -0.0, то в §6.3p2 ми маємо " [...] знак суми або різниці x - y, що розглядається як сума x + (−y), відрізняється щонайменше від одного з ознак додавання; ". Це змушує нас призначати -0.0як результат (-0.0) + (-0.0), тому що -0.0відрізняється за ознакою від жодного з доповнень, а +0.0відрізняється за ознакою від двох доданків, порушуючи цей пункт.
  • є +0.0, то це зводиться до випадку додавання, (+0.0) + (-0.0)розглянутого вище у «Справі доповнення» , який згідно §6.3p3 постановляється +0.0.

Оскільки для всіх випадків вхідне значення є законним як вихід, допустимо вважати x-0.0не-оп і x == x-0.0тавтологію.

Оптимізація змін цінності

Стандарт IEEE 754-2008 містить таку цікаву цитату:

IEEE 754 § 10.4 Буквальне значення та оптимізація змін цінності

[...]

Наступні перетворення, що змінюють значення, зберігають буквальне значення вихідного коду:

  • Застосування властивості ідентичності 0 + x, коли x не дорівнює нулю і не є сигналом NaN, і результат має той же показник, що і x.
  • Застосування властивості ідентичності 1 × x, коли x не є сигналом NaN, і результат має той же показник, що і x.
  • Зміна корисного вантажу або знака тихого NaN.
  • [...]

Оскільки всі NaN та всі нескінченності мають однаковий показник, а правильно округлений результат x+0.0та x*1.0для кінцевих xмає точно таку ж величину, як xі їх показник однаковий.

sNaNs

Сигналізаційні NaN - це пастки з плаваючою комою; Вони є спеціальними значеннями NaN, використання яких в якості операнда з плаваючою комою призводить до недійсного виключення операції (SIGFPE). Якби цикл, який викликає виняток, був оптимізований, програмне забезпечення більше не поводилось би так.

Однак, як в коментарях вказує user2357112 , стандарт C11 явно не визначає поведінку сигналізації NaNs ( sNaN), тому компілятору дозволяється припускати, що вони не виникають, і таким чином, що винятки, які вони викликають, також не трапляються. Стандарт C ++ 11 пропускає опис поведінки для сигналізації NaN, і, таким чином, також залишає її невизначеною.

Режими округлення

У альтернативних режимах округлення допустимі оптимізації можуть змінюватися. Наприклад, у режимі « Круглого до негативного» - нескінченність оптимізація x+0.0 -> xстає допустимою, але x-0.0 -> xстає забороненою.

Щоб GCC не передбачала режими та способи округлення за замовчуванням, експериментальний прапор -frounding-mathможна передати GCC.

Висновок

Clang та GCC , навіть на -O3, залишаються сумісними з IEEE-754. Це означає, що він повинен дотримуватися вищезазначених правил стандарту IEEE-754. x+0.0це не бит ідентичний з xдля всіх в xвідповідно з цими правилами, але x*1.0 можуть бути обрані так : Тобто, коли ми

  1. Дотримуйтесь рекомендації пройти незмінним корисним навантаженням, xколи це NaN.
  2. Залиште біт знака результату NaN без змін * 1.0.
  3. Дотримуйтесь розпорядження XOR біт знаку під час показника / продукту, коли xце не NaN.

Для активізації IEEE-754-небезпечної оптимізації (x+0.0) -> xпрапор -ffast-mathпотрібно передати Clang або GCC.


2
Caveat: що робити, якщо це сигнал NaN? (Я насправді думав, що це якось було причиною, але я не дуже знав, як це, тому запитав.)
користувач541686

6
@Mehrdad: Додаток F, (необов'язково) частина стандарту C, яка визначає прихильність C до IEEE 754, явно не охоплює сигналізацію NaN. (C11 F.2.1., Перший рядок: "Ця специфікація не визначає поведінку сигналізації NaNs.") Реалізації, які декларують відповідність Додатку F, залишаються вільними робити те, що вони хочуть із сигналізацією NaN. Стандарт C ++ має власну обробку IEEE 754, але як би там не було (я не знайомий), я сумніваюся, що він визначає і поведінку сигналізації NaN.
user2357112 підтримує Моніку

2
@Mehrdad: sNaN посилається на невизначене поведінку відповідно до стандарту (але це, мабуть, добре визначено платформою), тому компілятор компресії тут дозволений.
Джошуа

1
@ user2357112: Можливість відловлення помилок як побічний ефект для інакше невикористаних обчислень, як правило, заважає багато оптимізації; якщо результат обчислення іноді ігнорується, компілятор може корисно відкласти обчислення, поки він не зможе знати, чи буде використаний результат, але якщо обчислення подало б важливий сигнал, це може бути погано.
supercat

2
О, дивіться, питання, яке легітимно стосується як C, так і C ++, на яке точно відповідають обидві мови посиланням на єдиний стандарт. Чи змусить це люди менше скаржитися на питання, позначені як C, так і C ++, навіть якщо питання стосується спільності мови? На жаль, я думаю, що ні.
Кайл Странд

35

x += 0.0не є NOOP, якщо xє -0.0. Оптимізатор все одно може викреслити весь цикл, оскільки результати не використовуються. Взагалі важко сказати, чому оптимізатор приймає рішення, які він робить.


2
Я фактично розмістив це після того, як я щойно прочитав, чому x += 0.0це не-оп, але я подумав, що, мабуть, це не є причиною, оскільки весь цикл повинен бути оптимізований у будь-якому випадку. Я можу його придбати, це просто не настільки переконливо, як я сподівався ...
користувач541686

Враховуючи схильність об'єктно-орієнтованих мов створювати побічні ефекти, я думаю, що було б важко бути впевненим, що оптимізатор не змінює фактичну поведінку.
Роберт Харві

Це може бути причиною, оскільки long longоптимізація діє (це було з gcc, який поводиться однаково як мінімум вдвічі )
e2-e4

2
@ ringø: long longце інтегральний тип, а не тип IEEE754.
MSalters

1
А як же x -= 0, це те саме?
Віктор Мелгрен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.