과학 응용 프로그램에서 수치 최적화를하고 있습니다. 내가 주목 한 것은 GCC가 호출 pow(a,2)
을 컴파일 하여 호출 을 최적화 a*a
하지만 호출 pow(a,6)
이 최적화되지 않고 실제로 라이브러리 함수를 호출 pow
하여 성능이 크게 저하 된다는 것 입니다. 반대로, 실행 가능한 Intel C ++ Compilericc
는 라이브러리 호출을 제거합니다 pow(a,6)
.
궁금한 점은 GCC 4.5.1 및 옵션 ” ” pow(a,6)
을 a*a*a*a*a*a
사용하여 대체 할 때 -O3 -lm -funroll-loops -msse4
5 개의 mulsd
명령어를 사용한다는 것입니다 .
movapd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
내가 쓰는 경우 동안 (a*a*a)*(a*a*a)
, 그것은 생산합니다
movapd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm13, %xmm13
곱하기 명령어의 수를 3으로 줄 icc
입니다. 비슷한 동작을합니다.
컴파일러가이 최적화 트릭을 인식하지 못하는 이유는 무엇입니까?
답변
때문에 부동 소수점 수학 연관되지 않습니다 . 부동 소수점 곱셈에서 피연산자를 그룹화하는 방법은 답의 숫자 정확도에 영향을줍니다.
결과적으로, 대부분의 컴파일러는 응답이 동일하게 유지된다고 확신 할 수 없거나 수치 정확도에 신경 쓰지 않는다고 말하지 않는 한 부동 소수점 계산 순서를 매우 보수적으로 유지합니다. 예를 들면 : 옵션을 다시 연결 부동 소수점 연산에 GCC 수 있습니다 GCC의, 또는 심지어 속도에 대한 정확성을 더욱 적극적으로 트레이드 오프를 허용 옵션을 선택합니다.-fassociative-math
-ffast-math
답변
Lambdageek는 제대로 연관성은 부동 소수점 번호를 보유하지 않기 때문에,의 “최적화”라는 지적a*a*a*a*a*a
에이(a*a*a)*(a*a*a)
값을 변경할 수 있습니다. 이것이 C99에서 허용하지 않는 이유입니다 (컴파일러 플래그 또는 pragma를 통해 사용자가 특별히 허용하지 않는 한). 일반적으로 프로그래머는 자신이 한 이유로 자신이 한 일을 썼다는 것을 전제로하며, 컴파일러는이를 존중해야합니다. 원하는(a*a*a)*(a*a*a)
경우 쓰십시오.
그래도 글쓰기가 어려울 수 있습니다. 왜 컴파일러가 당신이 사용할 때 옳은 일을 할 수는 pow(a,6)
없습니까? 그렇게하는 것이 잘못 되기 때문입니다 . 수학 라이브러리가 좋은 플랫폼에서는 또는 pow(a,6)
보다 훨씬 더 정확합니다 . 일부 데이터를 제공하기 위해 Mac Pro에서 작은 실험을 수행하여 [1,2) 사이의 모든 단 정밀도 부동 숫자에 대해 a ^ 6을 평가할 때 최악의 오류를 측정했습니다.a*a*a*a*a*a
(a*a*a)*(a*a*a)
worst relative error using powf(a, 6.f): 5.96e-08
worst relative error using (a*a*a)*(a*a*a): 2.94e-07
worst relative error using a*a*a*a*a*a: 2.58e-07
pow
곱셈 트리 대신을 사용 하면 오차 4 의 오차 한계가 줄어 듭니다 . 컴파일러는 사용자가 라이센스를 부여하지 않은 경우 (예 :를 통해 -ffast-math
) 오류를 증가시키는 “최적화”를하지 않아야합니다 (일반적으로 ).
GCC는 __builtin_powi(x,n)
에 대한 대안으로 pow( )
인라인 곱셈 트리를 생성해야합니다. 성능의 정확성을 떨어 뜨리고 싶지만 빠른 계산을 사용하지 않으려는 경우에 사용하십시오.
답변
또 다른 유사한 경우 대부분의 컴파일러하지 않습니다 최적화 a + b + c + d
에 (a + b) + (c + d)
(즉,로 주어로하고 평가 (이 두 번째 표현이 더 나은 파이프 라인 될 수 있기 때문에 최적화가) (((a + b) + c) + d)
). 이것은 코너 케이스 때문입니다.
float a = 1e35, b = 1e-5, c = -1e35, d = 1e-5;
printf("%e %e\n", a + b + c + d, (a + b) + (c + d));
이 출력 1.000000e-05 0.000000e+00
답변
Fortran (과학 컴퓨팅 용으로 설계됨)에는 전원 연산자가 내장되어 있으며, 내가 아는 한 Fortran 컴파일러는 일반적으로 설명하는 것과 비슷한 방식으로 정수 전력을 올릴 수 있도록 최적화합니다. 불행히도 C / C ++에는 파워 연산자가없고 라이브러리 함수 만 있습니다 pow()
. 이것은 스마트 컴파일러가 pow
특수한 경우를 위해 특별하게 처리 하고 더 빠른 방식으로 계산 하는 것을 방해하지는 않지만 덜 일반적으로 사용되는 것 같습니다 …
몇 년 전에 나는 정수 전력을 최적의 방법으로 계산하는 것이 더 편리하도록 노력하고 있었고 다음을 생각해 냈습니다. 그것은 C가 아니라 C ++이며 여전히 최적화 / 인라인 방법에 대해 다소 똑똑한 컴파일러에 달려 있습니다. 어쨌든, 실제로 유용하게 사용될 수 있기를 바랍니다.
template<unsigned N> struct power_impl;
template<unsigned N> struct power_impl {
template<typename T>
static T calc(const T &x) {
if (N%2 == 0)
return power_impl<N/2>::calc(x*x);
else if (N%3 == 0)
return power_impl<N/3>::calc(x*x*x);
return power_impl<N-1>::calc(x)*x;
}
};
template<> struct power_impl<0> {
template<typename T>
static T calc(const T &) { return 1; }
};
template<unsigned N, typename T>
inline T power(const T &x) {
return power_impl<N>::calc(x);
}
궁금한 점에 대한 설명 : 이것은 전력을 계산하는 최적의 방법을 찾지 못하지만 최적의 솔루션을 찾는 것은 NP- 완전한 문제 이므로 (어떻게 사용하는 것과 달리) 작은 전력에 대해서만 가치가 pow
있기 때문에 소란 할 이유가 없습니다. 세부 사항으로.
그런 다음로 사용하십시오 power<6>(a)
.
이렇게하면 힘을 쉽게 입력 할 수 있고 (파 a
렌스로 6 초 를 철자 할 필요가 없음 ), 보상 합산-ffast-math
과 같은 정밀한 의존성이있는 경우 (작업 순서가 필수적인 예) 없이 이러한 종류의 최적화를 수행 할 수 있습니다. .
아마도 이것이 C ++임을 잊어 버릴 수 있으며 C 프로그램에서 사용하십시오 (C ++ 컴파일러로 컴파일하는 경우).
이것이 유용 할 수 있기를 바랍니다.
편집하다:
이것이 내 컴파일러에서 얻는 것입니다.
를 들어 a*a*a*a*a*a
,
movapd %xmm1, %xmm0
mulsd %xmm1, %xmm0
mulsd %xmm1, %xmm0
mulsd %xmm1, %xmm0
mulsd %xmm1, %xmm0
mulsd %xmm1, %xmm0
를 들어 (a*a*a)*(a*a*a)
,
movapd %xmm1, %xmm0
mulsd %xmm1, %xmm0
mulsd %xmm1, %xmm0
mulsd %xmm0, %xmm0
를 들어 power<6>(a)
,
mulsd %xmm0, %xmm0
movapd %xmm0, %xmm1
mulsd %xmm0, %xmm1
mulsd %xmm0, %xmm1
답변
GCC는 실제로 a가 정수일 때 최적화 a*a*a*a*a*a
합니다 (a*a*a)*(a*a*a)
. 나는이 명령으로 시도했다 :
$ echo 'int f(int x) { return x*x*x*x*x*x; }' | gcc -o - -O2 -S -masm=intel -x c -
gcc 플래그는 많지만 멋진 것은 없습니다. 그들은 의미한다 : stdin에서 읽는다; O2 최적화 수준을 사용하십시오. 이진 대신 출력 어셈블리 언어 목록; 리스팅은 인텔 어셈블리 언어 구문을 사용해야합니다. 입력은 C 언어입니다 (일반적으로 언어는 입력 파일 확장자에서 유추되지만 stdin에서 읽을 때 파일 확장자는 없습니다). 그리고 stdout에 씁니다.
출력의 중요한 부분은 다음과 같습니다. 어셈블리 언어로 무슨 일이 일어나고 있는지 나타내는 몇 가지 주석으로 주석을 달았습니다.
; x is in edi to begin with. eax will be used as a temporary register.
mov eax, edi ; temp = x
imul eax, edi ; temp = x * temp
imul eax, edi ; temp = x * temp
imul eax, eax ; temp = temp * temp
우분투 파생물 인 Linux Mint 16 Petra에서 시스템 GCC를 사용하고 있습니다. gcc 버전은 다음과 같습니다.
$ gcc --version
gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1
다른 포스터에서 언급했듯이 부동 소수점 산술은 연관성이 없으므로 부동 소수점에서는이 옵션을 사용할 수 없습니다.
답변
32 비트 부동 소수점 숫자 (예 : 1.024)는 1.024가 아니기 때문입니다. 컴퓨터에서 1.024는 (1.024-e)에서 (1.024 + e)까지의 간격입니다. 여기서 “e”는 오류를 나타냅니다. 어떤 사람들은 이것을 깨닫지 못하고 또한 *에서 *는 임의의 정밀도 숫자의 곱셈을 의미하며 그 숫자에 오류가 없음을 믿습니다. 일부 사람들이 이것을 깨닫지 못하는 이유는 아마도 초등학교에서 연습 한 수학 계산 일 것입니다. 오류가없는 이상적인 숫자로만 작업하고 곱셈을 수행하는 동안 단순히 “e”를 무시해도된다고 믿기 때문입니다. “float a = 1.2”, “a * a * a”및 유사한 C 코드에 “e”가 암시되어 있지 않습니다.
대부분의 프로그래머가 C 표현식 a * a * a * a * a * a가 실제로 이상적인 숫자와 함께 작동하지 않는다는 생각을 인식하고 실행할 수 있다면 GCC 컴파일러는 “a * a를 최적화 할 수 있습니다. * a * a * a * a “는”t = (a * a); t * t * t “로 말하면 더 적은 수의 곱셈이 필요합니다. 그러나 불행히도 GCC 컴파일러는 코드를 작성하는 프로그래머가 “a”가 오류가 있거나없는 숫자라고 생각하는지 여부를 알지 못합니다. 따라서 GCC는 소스 코드의 모양 만 수행합니다. 왜냐하면 그것이 “네이 키드 아이”로 GCC에 표시되기 때문입니다.
당신이 어떤 프로그래머 알고 나면 … 당신은 , 당신은 GCC를 말할 수있는 “-ffast – 수학”스위치를 사용할 수있다 “이봐, GCC, 나는 내가 뭐하는 거지 알아!”. 이를 통해 GCC는 a * a * a * a * a * a를 다른 텍스트 조각으로 변환 할 수 있습니다. a * a * a * a * a * a와는 다르게 보이지만 여전히 오류 간격 내에서 숫자를 계산합니다. a * a * a * a * a * a. 이상적인 숫자가 아닌 간격으로 작업하고 있다는 것을 이미 알고 있으므로 괜찮습니다.
답변
플로팅 표현식의 수축에 대해서는 아직 언급 한 포스터가 없습니다 (ISO C 표준, 6.5p8 및 7.12.2). 는 IF FP_CONTRACT
그마로 설정되어 ON
, 컴파일러는 같은 식 간주시킨다 a*a*a*a*a*a
번의 라운딩 정확하게 평가하는 것처럼, 하나의 동작으로서. 예를 들어, 컴파일러는 더 빠르고 정확한 내부 전력 함수로이를 대체 할 수 있습니다. 이는 최종 사용자가 제공 한 컴파일러 옵션이 때때로 잘못 사용될 수 있지만 동작은 소스 코드에서 프로그래머가 직접 동작을 부분적으로 제어하기 때문에 특히 흥미 롭습니다.
FP_CONTRACT
pragma 의 기본 상태 는 구현 정의이므로 컴파일러는 기본적으로 이러한 최적화를 수행 할 수 있습니다. 따라서 IEEE 754 규칙을 엄격하게 준수해야하는 이식 가능한 코드는 명시 적으로로 설정해야합니다 OFF
.
컴파일러가이 pragma를 지원하지 않는 경우 개발자가로 설정하도록 선택한 경우 이러한 최적화를 피함으로써 보수적이어야합니다 OFF
.
GCC는이 pragma를 지원하지 않지만 기본 옵션을 사용하면이 pragma를 가정합니다 ON
. 따라서 하드웨어 FMA가있는 대상의 경우 a*b+c
fma (a, b, c) 로의 변환을 방지하려면 -ffp-contract=off
(pragma를 명시 적으로로 설정 OFF
) 또는 -std=c99
(GCC에게 일부를 준수하도록 지시 하는) 옵션을 제공 해야합니다. C 표준 버전, 여기서 C99는 위 단락을 따릅니다). 과거에는 후자의 옵션이 변환을 방해하지 않았으므로 GCC 가이 시점에서 준수하지 않았 음을 의미합니다. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37845