your programing

GCC가 부동 소수점 표현식이 음수가 아닌 것으로 가정하도록 강제하는 방법은 무엇입니까?

lovepro 2020. 12. 25. 23:34
반응형

GCC가 부동 소수점 표현식이 음수가 아닌 것으로 가정하도록 강제하는 방법은 무엇입니까?


특정 부동 소수점식이 항상 음수가 아닌 것을 알고있는 경우가 있습니다. 벡터의 길이를 계산할 때 예를 들어, 하나는 않습니다 sqrt(a[0]*a[0] + ... + a[N-1]*a[N-1])(: 나는 NB을 나는 알고 std::hypot,이 문제와 관련이 없습니다), 그리고 제곱근 아래의 표현은 명확하게 음이 아닌 것입니다. 그러나 GCC 는 다음에 대해 다음 어셈블리를 출력 합니다 sqrt(x*x).

        mulss   xmm0, xmm0
        pxor    xmm1, xmm1
        ucomiss xmm1, xmm0
        ja      .L10
        sqrtss  xmm0, xmm0
        ret
.L10:
        jmp     sqrtf

즉,의 결과 x*x를 0과 비교하고 결과가 음수가 아니면 sqrtss명령을 수행하고 그렇지 않으면를 호출합니다 sqrtf.

그래서, 내 질문은 : 어떻게 GCC x*x가 항상 음수가 아닌 것으로 가정 하여 sqrtf인라인 어셈블리를 작성하지 않고 비교와 호출을 건너 뛰도록 할 수 있습니까?

나는 로컬 솔루션에 관심이 있음을 강조하고자하는, 그리고 일을하지 것들 좋아 -ffast-math, -fno-math-errno또는 -ffinite-math-only(이 실제로 문제를 해결 않습니다하지만, 덕분에 ks1322, 코멘트에 해롤드, 에릭 Postpischil).

또한 "GCC x*x가 음수가 아닌 것으로 가정하도록 강제 "는로 해석되어야 assert(x*x >= 0.f)하므로 x*xNaN 인 경우도 제외됩니다 .

컴파일러 별, 플랫폼 별, CPU 별 등의 솔루션은 괜찮습니다.


assert(x*x >= 0.f)GNU C에서 다음과 같이 런타임 검사 대신 컴파일 시간 약속으로 작성할 수 있습니다 .

#include <cmath>

float test1 (float x)
{
    float tmp = x*x;
    if (!(tmp >= 0.0f)) 
        __builtin_unreachable();    
    return std::sqrt(tmp);
}

(관련 : __builtin_unreachable은 어떤 최적화를 촉진합니까?if(!x)__builtin_unreachable() 매크로로 래핑 하고 그것을 호출 할 promise()수도 있습니다.)

그러나 gcc는 tmpNaN 이 아니고 음수가 아닌 약속을 활용하는 방법을 모릅니다 . 우리는 여전히 ( Godbolt ) set 을 확인 x>=0하고 호출 하는 동일한 canned asm sequence를 얻습니다 . 아마도 비교 및 ​​분기로의 확장은 다른 최적화를 통과 한 후에 발생 하므로 컴파일러가 더 많은 것을 아는 것은 도움이되지 않습니다.sqrtferrno

이것은 활성화 sqrt되었을 때 추측 적으로 인라인되는 로직에서 최적화되지 않은 것입니다 -fmath-errno(불행하게도 기본적으로 켜져 있음).

대신 원하는 -fno-math-errno것은 전 세계적으로 안전합니다.

수학 함수 설정에 의존하지 않는 경우 100 % 안전합니다errno . 아무도 그것을 원하지 않습니다. 그것이 마스킹 된 FP 예외를 기록하는 NaN 전파 및 / 또는 스티키 플래그의 목적입니다. 예를 들어 C99 / C ++ 11을 fenv통해 액세스 #pragma STDC FENV_ACCESS ON한 다음 fetestexcept(). feclearexcept0으로 나누기를 감지하는 데 사용 하는 예를 참조하십시오 .

FP 환경은 스레드 컨텍스트의 일부 errno이며 글로벌입니다.

이 오래된 잘못된 기능에 대한 지원은 무료가 아닙니다. 그것을 사용하기 위해 작성된 오래된 코드가 없다면 그냥 꺼야합니다. 새 코드에서 사용하지 마십시오 fenv.. 이상적으로에 대한 지원은 -fmath-errno가능한 한 저렴하지만 실제로 __builtin_unreachable()NaN 입력을 배제하기 위해 실제로 사용하는 사람 이나 기타 항목이 거의 없기 때문에 개발자가 최적화를 구현하는 데 시간을 낭비 할 가치가 없었을 것입니다. 그래도 원하는 경우 누락 된 최적화 버그를보고 할 수 있습니다.

실제 FPU 하드웨어에는 실제로 지워질 때까지 설정된 상태로 유지되는 이러한 고정 플래그가 있습니다 (예 : SSE / AVX 연산을위한 x86의mxcsr 상태 / 제어 레지스터 또는 다른 ISA의 하드웨어 FPU). FPU가 예외를 감지 할 수있는 하드웨어에서 고품질 C ++ 구현은 fetestexcept(). 그렇지 않다면 수학도 errno작동하지 않을 것입니다.

errno수학을 위해 C / C ++는 여전히 기본적으로 고착되어있는 오래된 구식 디자인이었으며 이제는 널리 나쁜 아이디어로 간주됩니다. 컴파일러가 수학 함수를 효율적으로 인라인하기 어렵게 만듭니다. 아니면 내가 생각했던 것만 큼 집착하지 않았을 수도 있습니다. 왜 sqrt가 도메인 논쟁에서 벗어나더라도 errno가 EDOM으로 설정되지 않은 이유는 무엇입니까? 수학 함수에서 errno를 설정하는 것은 ISO C11에서 선택 사항 이며 구현이 수행 여부를 나타낼 수 있다고 설명합니다. 아마도 C ++에서도 마찬가지입니다.

그것은 덩어리에 큰 실수 -fno-math-errno값 변화 최적화 좋아에 -ffast-math-ffinite-math-only. 전체적으로 또는 적어도이 기능을 포함하는 전체 파일에 대해 활성화하는 것을 강력히 고려해야합니다.

float test2 (float x)
{
    return std::sqrt(x*x);
}
# g++ -fno-math-errno -std=gnu++17 -O3
test2(float):   # and test1 is the same
        mulss   xmm0, xmm0
        sqrtss  xmm0, xmm0
        ret

You might as well use -fno-trapping-math as well, if you aren't ever going to unmask any FP exceptions with feenableexcept(). (Although that option isn't required for this optimization, it's only the errno-setting crap that's a problem here.).

-fno-trapping-mathNaN이 없다고 가정하지 않으며 Invalid 또는 Inexact와 같은 FP 예외가 NaN 또는 반올림 된 결과를 생성하는 대신 실제로 신호 처리기를 호출하지 않는다고 가정합니다. -ftrapping-math기본값이지만 GCC 개발자 Marc Glisse에 따르면 고장 났고 "절대 작동하지 않았습니다" . (켜져 있어도 GCC는 0에서 0이 아닌 것으로 또는 그 반대로 발생하는 예외의 수를 변경할 수있는 몇 가지 최적화를 수행합니다. 그리고 일부 안전한 최적화를 차단합니다). 그러나 유감스럽게도 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54192 (기본적으로 해제)는 여전히 열려 있습니다.

If you actually ever did unmask exceptions, it might be better to have -ftrapping-math, but again it's very rare that you'd ever want that instead of just checking flags after some math operations, or checking for NaN. And it doesn't actually preserve exact exception semantics anyway.

See SIMD for float threshold operation for a case where -fno-trapping-math incorrectly blocks a safe optimization. (Even after hoisting a potentially-trapping operation so the C does it unconditionally, gcc makes non-vectorized asm that does it conditionally! So not only does it block vectorization, it changes the exception semantics vs. the C abstract machine.)


Pass the option -fno-math-errno to gcc. This fixes the problem without making your code unportable or leaving the realm of ISO/IEC 9899:2011 (C11).

What this option does is not attempting to set errno when a math library function fails:

       -fno-math-errno
           Do not set "errno" after calling math functions that are executed
           with a single instruction, e.g., "sqrt".  A program that relies on
           IEEE exceptions for math error handling may want to use this flag
           for speed while maintaining IEEE arithmetic compatibility.

           This option is not turned on by any -O option since it can result
           in incorrect output for programs that depend on an exact
           implementation of IEEE or ISO rules/specifications for math
           functions. It may, however, yield faster code for programs that do
           not require the guarantees of these specifications.

           The default is -fmath-errno.

           On Darwin systems, the math library never sets "errno".  There is
           therefore no reason for the compiler to consider the possibility
           that it might, and -fno-math-errno is the default.

Given that you don't seem to be particularly interested in math routines setting errno, this seems like a good solution.


Without any global options, here is a (low-overhead, but not free) way to get a square root with no branch:

#include <immintrin.h>

float test(float x)
{
    return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set1_ps(x * x)));
}

(on godbolt)

As usual, Clang is smart about its shuffles. GCC and MSVC lag behind in that area, and don't manage to avoid the broadcast. MSVC is doing some mysterious moves as well..

There are other ways to turn a float into an __m128, for example _mm_set_ss. For Clang that makes no difference, for GCC that makes the code a little bigger and worse (including a movss reg, reg which counts as a shuffle on Intel, so it doesn't even save on shuffles).


After about a week, I asked on the matter on GCC Bugzilla & they've provided a solution which is the closest to what I had in mind

float test (float x)
{
    float y = x*x;
    if (std::isless(y, 0.f))
        __builtin_unreachable();
    return std::sqrt(y);
}

that compiles to the following assembly:

test(float):
    mulss   xmm0, xmm0
    sqrtss  xmm0, xmm0
    ret

I'm still not quite sure what exactly happens here, though.

ReferenceURL : https://stackoverflow.com/questions/57673825/how-to-force-gcc-to-assume-that-a-floating-point-expression-is-non-negative

반응형