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*x
NaN 인 경우도 제외됩니다 .
컴파일러 별, 플랫폼 별, 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는 tmp
NaN 이 아니고 음수가 아닌 약속을 활용하는 방법을 모릅니다 . 우리는 여전히 ( Godbolt ) set 을 확인 x>=0
하고 호출 하는 동일한 canned asm sequence를 얻습니다 . 아마도 비교 및 분기로의 확장은 다른 최적화를 통과 한 후에 발생 하므로 컴파일러가 더 많은 것을 아는 것은 도움이되지 않습니다.sqrtf
errno
이것은 활성화 sqrt
되었을 때 추측 적으로 인라인되는 로직에서 최적화되지 않은 것입니다 -fmath-errno
(불행하게도 기본적으로 켜져 있음).
대신 원하는 -fno-math-errno
것은 전 세계적으로 안전합니다.
수학 함수 설정에 의존하지 않는 경우 100 % 안전합니다errno
. 아무도 그것을 원하지 않습니다. 그것이 마스킹 된 FP 예외를 기록하는 NaN 전파 및 / 또는 스티키 플래그의 목적입니다. 예를 들어 C99 / C ++ 11을 fenv
통해 액세스 #pragma STDC FENV_ACCESS ON
한 다음 fetestexcept()
. feclearexcept
0으로 나누기를 감지하는 데 사용 하는 예를 참조하십시오 .
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-math
NaN이 없다고 가정하지 않으며 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
'your programing' 카테고리의 다른 글
자바 스크립트 데이터 구조 라이브러리 (0) | 2020.12.25 |
---|---|
Node.js Express 프레임 워크 보안 문제 (0) | 2020.12.25 |
bcrypt보다 scrypt의 장점은 무엇입니까? (0) | 2020.12.25 |
JOIN 문 (SQL)에서 정확히 "왼쪽"테이블과 "오른쪽"테이블은 무엇입니까? (0) | 2020.12.25 |
SW를 호스팅하는 최고의 자식은 무엇입니까? (0) | 2020.12.25 |