g ++에서 최적화 수준 -O3이 위험합니까?


232

나는 -O3g ++에서 최적화 수준으로 컴파일하는 것이 어쨌든 '위험한'것이며, 필요하다고 입증되지 않는 한 일반적으로 피해야한다는 다양한 소스 (주로 동료 동료로부터)를 들었습니다 .

이것이 사실입니까? 그렇다면 왜 그렇습니까? 난 그냥 고집해야 -O2합니까?


38
정의되지 않은 행동에 의존하는 경우에만 위험합니다. 그리고 그때조차도 무언가를 엉망으로 만든 최적화 수준이라면 놀랄 것입니다.
세스 카네기

5
컴파일러는 여전히 "있는 것처럼"동작하는 프로그램을 생성하여 사용자의 코드를 정확하게 컴파일하도록 제한되어 있습니다. 나는 그것이 -O3특히 버그로 간주 되는 것을 모른다 ? 나는 그것이 특정 가정에 기초하여 기묘하고 훌륭한 일을 할 수 있기 때문에 정의되지 않은 행동을 "나쁘게"만들 수 있다고 생각하지만 그것은 당신 자신의 잘못 일 것입니다. 그래서 일반적으로 괜찮습니다.
BoBTFish

5
최적화 수준이 높을수록 컴파일러 버그가 발생하기 쉽다는 것이 사실입니다. 나는 몇 가지 경우를 겪었지만 일반적으로 여전히 거의 드문 경우입니다.
신비주의

21
-O2을 켜고 -fstrict-aliasing코드가 살아남 으면 다른 최적화에서도 살아남을 것입니다. 왜냐하면 사람들이 반복해서 잘못하고 있기 때문입니다. 즉, 말했다 -fpredictive-commoning만에 -O3, 그리고 그것을 가능하게 동시성에 대한 잘못된 가정으로 인한 코드에서 버그를 수 있습니다. 코드가 잘못 될수록 덜 위험한 최적화는 ;-)입니다.
Steve Jessop

6
@PlasmaHH, "stricter"는 좋은 설명이 아니라고 생각 -Ofast합니다. 예를 들어 NaN의 IEEE 호환 처리를 해제합니다.
Jonathan Wakely

답변:


223

gcc의 초기 (2.8 등)와 egc의 시대, 그리고 redhat 2.96 -O3은 때때로 꽤 버그가 많았습니다. 그러나 이것은 10 년 전에 이루어졌으며 -O3는 다른 수준의 최적화 (버기)와 크게 다르지 않습니다.

그러나 그것은 언어의 규칙, 특히 모퉁이 경우에 더 엄격하게 의존하기 때문에 사람들이 정의되지 않은 행동에 의존하는 경우를 드러내는 경향이 있습니다.

개인적으로, 저는 -O3을 사용하여 몇 년 동안 금융 부문에서 프로덕션 소프트웨어를 실행하고 있으며 -O2를 사용했을 경우에는 없었던 버그가 발생하지 않았습니다.

인기있는 수요에 따라 여기에 추가 사항이 있습니다.

-O3 및 특히 -funroll-loops (-O3에 의해 활성화되지 않음)와 같은 추가 플래그는 때때로 더 많은 머신 코드를 생성 할 수 있습니다. 특정 상황에서 (예 : 매우 작은 L1 명령어 캐시가있는 CPU에서) 이로 인해 내부 루프의 모든 코드가 더 이상 L1I에 더 이상 맞지 않기 때문에 속도가 느려질 수 있습니다. 일반적으로 gcc는 너무 많은 코드를 생성하지 않으려 고 노력하지만 일반적으로 일반적인 경우를 최적화하기 때문에 발생할 수 있습니다. 루프 언 롤링과 같이 특히 이러한 경향이있는 옵션은 일반적으로 -O3에 포함되지 않으며 맨 페이지에 표시됩니다. 따라서 일반적으로 빠른 코드를 생성하기 위해 -O3을 사용하는 것이 좋으며, 적절한 경우 (예 : 프로파일 러가 L1I가 누락되었음을 나타내는 경우) -O2 또는 -O (코드 크기를 최적화하려고 시도)로만 넘어가는 것이 좋습니다.

최적화를 최대한 활용하려면 특정 최적화와 관련된 비용을 --param을 통해 gcc로 조정할 수 있습니다. 또한 gcc는 이제 이러한 함수에 대해서만 최적화 설정을 제어하는 ​​함수에 속성을 넣을 수있는 기능이 있으므로 한 함수에서 -O3에 문제가 있거나 해당 함수에 대한 특수 플래그를 시도하려는 경우, O2로 전체 파일 또는 전체 프로젝트를 컴파일 할 필요가 없습니다.

-Ofast를 사용할 때는 다음과 같은주의가 필요합니다.

-Ofast는 모든 -O3 최적화를 활성화합니다. 또한 모든 표준 호환 프로그램에 유효하지 않은 최적화를 활성화합니다.

-O3은 표준을 완벽하게 준수한다는 결론을 내 렸습니다.


2
나는 단지 반대와 같은 것을 사용합니다. 나는 항상 -Os 또는 -O2를 사용한다 (때로는 O2가 더 작은 실행 파일을 생성한다).
CoffeDeveloper 2016

3
나는 속도를 위해 그것을한다. O3는 대부분 속도를 느리게합니다. 왜 그런지 정확히 모르겠습니다. 명령 캐시를 오염시키는 것으로 의심됩니다.
CoffeDeveloper

4
@DarioOO 나는 "코드 팽창"을 호소하는 것이 인기있는 일이라고 생각하지만 벤치 마크로 뒷받침되는 것을 거의 보지 못했습니다. 아키텍처에 많이 의존하지만 게시 된 벤치 마크 (예 : phoronix.com/…)를 볼 때마다 )를 대다수의 경우 O3가 더 빠릅니다. 코드 팽창이 실제로 문제임을 증명하기 위해 필요한 프로파일 링과 신중한 분석을 보았으며 템플릿을 극단적으로 받아들이는 사람들에게만 발생합니다.
Nir Friedman

1
@NirFriedman : 컴파일러의 인라인 비용 모델에 버그가 있거나 실행하는 것과 완전히 다른 목표에 대해 최적화 할 때 문제가 발생하는 경향이 있습니다. 이것은 모든 최적화 수준에 적용됩니다 ...
PlasmaHH

1
@ PlasmaHH : using-cmov 문제는 일반적인 경우 해결하기가 어렵습니다. 일반적으로 당신이하지 않은 단지 GCC는 분기 예측 가능 여부를 결정하려고 그렇게 할 때, 데이터를 정렬에 대한 호출을 찾고 정적 분석 std::sort기능을 도와 않을 수 있습니다. stackoverflow.com/questions/109710/… 와 같은 것을 사용 하면 정렬을 활용하는 데 도움이되거나 소스를 작성할 수 있습니다.> = 128이 보일 때까지 스캔 한 다음 합산을 시작하십시오. 부풀어 오른 코드는 그렇습니다.보고하려고합니다. : P
Peter Cordes

42

다소 체크 무늬가있는 경험에서 -O3전체 프로그램에 적용 하면 프로그램이 더 이상 명령 캐시에 더 이상 적합하지 않게하는 적극적인 루프 언 롤링과 인라이닝이 켜지 기 때문에 거의 항상 느려집니다 (에 상대적 -O2). 큰 프로그램의 경우,이 또한 마찬가지가 될 수 -O2를 기준으로 -Os!

의도 된 사용 패턴 -O3 은 프로그램을 프로파일 링 한 후 이러한 공격적인 속도-공간 속도 균형에서 실제로 혜택을받는 중요한 내부 루프가 포함 된 작은 파일에 수동으로 적용하는 것입니다. 최신 버전의 GCC에는 (IIUC) -O3최적화를 핫 기능에 선택적으로 적용 하여이 프로세스를 효과적으로 자동화 할 수있는 프로파일 가이드 최적화 모드가 있습니다 .


10
"거의 언제나"? "50-50"으로 설정하면 ;-) 거래가 발생합니다.
No-Bugs Hare

12

-O3 옵션은 하위 레벨 '-O2'및 '-O1'의 모든 최적화 외에도 함수 인라인과 같은 더 비싼 최적화를 켭니다. '-O3'최적화 수준은 결과 실행 파일의 속도를 증가시킬 수 있지만 크기를 늘릴 수도 있습니다. 이러한 최적화가 바람직하지 않은 일부 환경에서는이 옵션으로 인해 실제로 프로그램 속도가 느려질 수 있습니다.


3
일부 "명확한 최적화"로 인해 프로그램이 느려질 수 있지만 GCC -O3이 프로그램을 느리게 만들었다 고 주장하는 출처가 있습니까?
Mooing Duck

1
@MooingDuck : 소스를 인용 할 수 없지만, L1I 캐시가 매우 작은 일부 오래된 AMD 프로세서 (~ 10k 명령)를 사용하는 경우가 있습니다. Google은 관심을 가질만한 것이 많지만 특히 루프 언 롤링과 같은 옵션은 O3의 일부가 아니며 크기가 많이 증가합니다. -Os는 실행 파일을 가장 작게 만들 때 사용합니다. -O2라도 코드 크기를 늘릴 수 있습니다. 다른 최적화 수준의 결과를 다루는 좋은 도구는 gcc 탐색기입니다.
PlasmaHH

@PlasmaHH : 실제로 작은 캐시 크기는 컴파일러가 망칠 수있는 것입니다. 정말 좋은 예입니다. 답을 넣으십시오.
Mooing Duck

1
@PlasmaHH Pentium III에는 16KB 코드 캐시가있었습니다. AMD의 K6 이상에는 실제로 32KB 명령 캐시가있었습니다. P4는 약 96KB의 가치로 시작했습니다. 코어 I7에는 실제로 32KB L1 코드 캐시가 있습니다. 오늘날 명령어 디코더는 강력하므로 L3은 거의 모든 루프에서 대체 할 수있을 정도로 좋습니다.
doug65536

1
루프에서 호출되는 함수가있을 때마다 성능이 크게 향상되는 것을 볼 수 있으며, 함수에서 루프 외부로 불필요한 재 계산을 제거하고 상당한 공통 하위 표현식을 제거 할 수 있습니다.
doug65536

8

예, O3는 버그가 많습니다. 나는 컴파일러 개발자이며 O3가 자체 소프트웨어를 빌드 할 때 버그가있는 SIMD 어셈블리 지침을 생성하여 발생하는 명확하고 명백한 gcc 버그를 식별했습니다. 내가 본 것에서 대부분의 프로덕션 소프트웨어는 O2와 함께 제공됩니다. 즉, O3은 wrt 테스트 및 버그 수정에 대한 관심을 덜 받게됩니다.

O3는 O2 위에 더 많은 변환을 추가하여 O1 위에 더 많은 변환을 추가합니다. 통계적으로 말하면 더 많은 변환은 더 많은 버그를 의미합니다. 그것은 모든 컴파일러에 해당됩니다.


3

최근에로 최적화를 사용하는 데 문제가 발생했습니다 g++. 이 문제는 PCI 카드와 관련이 있는데 레지스터 (명령 및 데이터 용)는 메모리 주소로 표시되었습니다. 내 드라이버는 실제 주소를 응용 프로그램 내의 포인터에 매핑하고 호출 된 프로세스에 제공했으며 다음과 같이 작동했습니다.

unsigned int * pciMemory;
askDriverForMapping( & pciMemory );
...
pciMemory[ 0 ] = someCommandIdx;
pciMemory[ 0 ] = someCommandLength;
for ( int i = 0; i < sizeof( someCommand ); i++ )
    pciMemory[ 0 ] = someCommand[ i ];

카드가 예상대로 작동하지 않았습니다. 내가 어셈블리를 보았을 때 나는 컴파일러는 쓴 이해 someCommand[ the last ]pciMemory선행하는 모든 쓰기를 생략.

결론 : 최적화를 통해 정확하고 세심한주의를 기울이십시오.


38
그러나 여기서 요점은 프로그램에 정의되지 않은 동작이 있다는 것입니다. 옵티마이 저는 아무 잘못도 없었다. 특히 당신은 선언 할 필요가 pciMemoryvolatile.
Konrad Rudolph

11
실제로 UB는 아니지만 컴파일러는 pciMemory다른 모든 쓰기가 효과가 없기 때문에 마지막 쓰기를 제외한 모든 쓰기를 생략 할 권리가 있습니다. 옵티 마이저에게는 쓸모없고 시간이 많이 걸리는 많은 명령을 제거 할 수 있기 때문에 훌륭합니다.
Konrad Rudolph

4
나는 이것을 10 년 이상 후에 표준에서 찾았다))- 휘발성 선언은 메모리 매핑 된 입 / 출력 포트에 해당하는 객체 또는 비동기식 인터럽트 기능에 의해 액세스되는 객체를 설명하는 데 사용될 수 있습니다. 그렇게 선언 된 객체에 대한 행동은 표현 평가 규칙에 의해 허용 된 경우를 제외하고는 구현에 의해``최적화 ''되거나 재정렬되지 않아야한다.
borisbn

2
@borisbn 주제가 다소 다르지만 새 명령을 보내기 전에 장치가 명령을 받았다는 것을 어떻게 알 수 있습니까?
user877329

3
@ user877329 장치의 동작으로 보았지만 큰 퀘스트였습니다.
borisbn
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.