Clang은 왜 x * 1.0을 최적화하지만 x + 0.0은 최적화하지 않습니까? start = clock();

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 ++로 태깅합니다.)



답변

부동 소수점 산술을위한 IEEE 754-2008 표준 및 ISO / IEC 10967 LIA (Language Independent Arithmetic) 표준, 1 부는 이것이 왜 그런지에 대한 답변입니다.

IEEE 754 § 6.3 부호 비트

입력 또는 결과가 NaN 인 경우이 표준은 NaN의 부호를 해석하지 않습니다. 그러나 비트 문자열 (copy, negate, abs, copySign)에 대한 작업은 NaN 피연산자의 부호 비트에 따라 NaN 결과의 부호 비트를 지정합니다. 논리 술어 totalOrder도 NaN 피연산자의 부호 비트의 영향을받습니다. 다른 모든 작업의 ​​경우이 표준은 입력 NaN이 하나만 있거나 유효하지 않은 작업으로 NaN이 생성 된 경우에도 NaN 결과의 부호 비트를 지정하지 않습니다.

입력 값이나 결과가 NaN이 아닌 경우 곱 또는 부호의 부호는 피연산자 부호의 배타적 OR입니다. 합의 부호 또는 합 x + (-y)로 간주되는 차이 x-y는 최대 부수 부호와 다릅니다. 변환 결과의 부호, 양자화 연산, roundTo-Integral 연산 및 roundToIntegralExact (5.3.1 참조)는 첫 번째 또는 유일한 피연산자의 부호입니다. 이 규칙은 피연산자 또는 결과가 0 또는 무한 인 경우에도 적용됩니다.

반대 부호를 가진 두 피연산자의 합 (또는 같은 부호를 가진 두 피연산자의 차이)이 정확히 0 인 경우 roundTowardNegative를 제외한 모든 반올림 방향 속성에서 해당 합 (또는 차이)의 부호는 +0이어야합니다. 이 속성 하에서, 정확한 제로섬 (또는 차이)의 부호는 -0이어야한다. 그러나 x가 0 인 경우에도 x + x = x − (−x)는 x와 동일한 부호를 유지합니다.

덧셈의 ​​경우

기본 반올림 모드 (라운드에서 가장 가까운, 타이 에서 에븐까지 )에서 다음과 같은 경우를 제외 하고 x+0.0생성합니다 . 이 추가로 생성되는 3 가지 규칙 .xx-0.0+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출력 값은 입력에서 변경되지 않습니다. 따라서 (음수) 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.0no-op 및 x == x-0.0타우 톨로지를 고려할 수 있습니다.

가치 변화 최적화

IEEE 754-2008 표준에는 다음과 같은 흥미로운 인용문이 있습니다.

IEEE 754 § 10.4 리터럴 의미 및 가치 변경 최적화

[…]

다음과 같은 값 변경 변환은 소스 코드의 문자 적 ​​의미를 유지합니다.

  • x가 0이 아니고 신호 NaN이 아니고 결과가 x와 동일한 지수를 갖는 경우 항등 속성 0 + x를 적용합니다.
  • x가 신호 NaN이 아니고 결과가 x와 동일한 지수를 갖는 경우 항등 성 1 × x를 적용합니다.
  • 조용한 NaN의 페이로드 또는 부호 비트 변경
  • […]

모든 NaN이 모든 무한이 같은 지수, 그리고 올바르게 둥근 결과를 공유하기 때문에 x+0.0x*1.0유한에 대해 x정확히 같은 크기 등이있다x , 그들의 지수는 동일합니다.

sNaNs

시그널링 NaN은 부동 소수점 트랩 값입니다. 부동 소수점 피연산자로 사용하면 SIGFPE (잘못된 연산 예외)가 발생하는 특수 NaN 값입니다. 예외를 트리거하는 루프가 최적화 된 경우 소프트웨어는 더 이상 동일하게 동작하지 않습니다.

그러나 user2357112 가 주석에서 지적한 바와 같이 C11 표준은 NaN 신호의 동작을 정의되지 않은 채로 둡니다 (sNaN ) 므로 컴파일러는 발생하지 않는다고 가정 할 수 있으므로 발생하는 예외도 발생하지 않습니다. C ++ 11 표준은 NaN 신호에 대한 동작을 설명하지 않으므로 정의되지 않은 상태로 둡니다.

반올림 모드

대체 라운딩 모드에서는 허용되는 최적화가 변경 될 수 있습니다. 예를 들어, Round-to-Negative-Infinity 모드에서는 최적화 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. xNaN 인 경우 페이로드를 변경하지 말 것을 권장합니다 .
  2. NaN 결과의 부호 비트는 다음과 같이 변경하지 마십시오. * 1.0 .
  3. NaN x아닌 경우 몫 / 제품 중에 부호 비트를 XOR하는 순서를 따르십시오 .

IEEE-754 안전하지 않은 최적화 (x+0.0) -> x를 사용하려면 플래그 -ffast-math를 Clang 또는 GCC에 전달해야합니다.


답변

x += 0.0경우 NOOP 아니다 x됩니다 -0.0. 그래도 결과가 사용되지 않으므로 최적화 프로그램이 전체 루프를 제거 할 수 있습니다. 일반적으로 옵티마이 저가 결정을 내리는 이유를 말하기는 어렵습니다.