일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 머신러닝
- 데이터베이스
- I'm Sorry
- 네트워크
- 사이버보안
- 2
- Yes
- 코딩
- 빅데이터
- 클라우드컴퓨팅
- 딥러닝
- 파이썬
- 데이터분석
- 데이터구조
- 버전관리
- 프로그래밍
- 웹개발
- 소프트웨어공학
- 알고리즘
- 인공지능
- 자료구조
- 보안
- 컴퓨터비전
- 소프트웨어
- 자바스크립트
- 컴퓨터공학
- 컴퓨터과학
- 데이터과학
- 프로그래밍언어
- 네트워크보안
- Today
- Total
스택큐힙리스트
gcc 최적화 플래그 -O3은 -O2보다 코드를 느리게 만듭니다. 본문
나는이 주제를 - 번역기의 오류입니다! - 찾아봅니다. 그리고 이 코드를 실행하려고 시도합니다. 그런데 이상한 동작을 발견합니다. 만약 나는 이 코드를 '-O3' 최적화 플래그로 컴파일한다면 실행하는 데 '-O3' 정도 걸립니다. 만약 나는 #$$**$&$*$&로 컴파일한다면 실행하는 데 '1.98093 sec' 정도 걸립니다. 동일한 기계와 같은 환경에서 이 코드를 여러 번(5 또는 6번) 실행해보려고 합니다. 기타 소프트웨어(크롬, 스카이프 등)를 모두 닫습니다.
'gcc --version
gcc (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
'
그러니까 이것이 왜 일어나는지 설명해 줄 수 있을까요? 나는 #@**@^&$& 매뉴얼을 읽고, #@^*!#^$$&이 *^**&$&를 포함한다는 것을 알았어요. 도움 주셔서 감사합니다.
번역하세요.
P.S. 코드를 추가해주세요.
'#include
#include
#include
int main()
{
// Generate data
const unsigned arraySize = 32768;
int data[arraySize];
for (unsigned c = 0; c < arraySize; ++c)
data[c] = std::rand() % 256;
// !!! With this, the next loop runs faster
std::sort(data, data + arraySize);
// Test
clock_t start = clock();
long long sum = 0;
for (unsigned i = 0; i < 100000; ++i)
{
// Primary loop
for (unsigned c = 0; c < arraySize; ++c)
{
if (data[c] >= 128)
sum += data[c];
}
}
double elapsedTime = static_cast
std::cout << elapsedTime << std::endl;
std::cout << sum = << sum << std::endl;
}
'
답변 1
'gcc -O3'는 조건에 대해 'cmov'를 사용하여 루프에 의존성 체인을 연장하므로 이는 인텔 샌디브리지 CPU에서 2 개의 uop와 2개의 사이클 지연을 가지게 됩니다. (이는 'Agner Fog's instruction tables' 에 따르면, 'x86' 태그 위키도 참조하십시오). 이것은 'one of the cases where cmov sucks' 입니다.
만약 데이터가 예측할 수 없을 정도로 불안정하다면, 'cmov' 가 이길 수도 있으므로, 컴파일러에게는 이러한 선택이 상당히 합리적인 선택일 것입니다. (하지만, 'compilers may sometimes use branchless code too much' .)
저는 asm을 보고 싶어요 (아름다운 하이라이팅이 있으며 관련 없는 줄은 걸러낼 수 있어요. 하지만 여전히 모든 정렬 코드를 지나서 main()까지 스크롤해야 합니다).
'.L82: # the inner loop from gcc -O3
movsx rcx, DWORD PTR [rdx] # sign-extending load of data[c]
mov rsi, rcx
add rcx, rbx # rcx = sum+data[c]
cmp esi, 127
cmovg rbx, rcx # sum = data[c]>127 ? rcx : sum
add rdx, 4 # pointer-increment
cmp r12, rdx
jne .L82
'
gcc는 ADD 대신에 LEA를 사용하여 MOV를 절약할 수 있었을 것이다.
루프는 ADD->CMOV의 지연시간(3 사이클)에 의해 병목 현상이 발생합니다. 왜냐하면 루프의 한 번 반복에서는 CMO를 사용하여 rbx에 쓰고, 다음 반복에서는 ADD를 사용하여 rbx를 읽기 때문입니다.
루프에는 8개의 퓨즈 도메인 uop만 포함되어 있으므로 2사이클당 하나씩 실행될 수 있습니다. 실행 포트 압력도 'sum' 종속 체인의 지연만큼 심각한 병목 현상은 아니지만 근접합니다 (Sandybridge는 4개가 아닌 3개의 ALU 포트를 가지고 있습니다).
그런데, 루프-캐리어드-디프 체인에서 탈출하기 위해 'sum += (data[c] >= 128 ? data[c] : 0);'로 작성하는 것은 잠재적으로 유용할 수 있습니다. 여전히 많은 명령이 있지만 각 반복에서 발생하는 'cmov'는 독립적입니다. 이는 piles as expected in gcc6.3 -O2 and earlier' , but 입니다. 그러나 gcc7에서는 크리티컬 패스 상에서 a 'cmov'로 디-최적화 됩니다 ( 'https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82666' ). (이전 gcc 버전에서도 자동 벡터화되며 이를 작성하는 'if()' 방식보다 쉽습니다.)
클랭은 원본 소스와 함께 cmov를 비중요 경로에서 제거합니다.
'gcc -O2'은(는) 분기를 사용합니다(gcc5.x 이전 버전을 위해), 이는 데이터가 정렬되어 있기 때문에 예측을 잘합니다. 현대의 CPU는 분기 예측을 사용하여 제어 종속성을 처리하기 때문에 루프 범위의 종속성 체인이 짧아집니다: 딱 한 사이클의 지연인 'add' 입니다.
각 반복에서의 비교 및 분기는 분기 예측과 추측 실행 덕분에 독립적이며, 분기 방향이 확실히 알려지기 전에 실행을 계속할 수 있게 합니다.
'.L83: # The inner loop from gcc -O2
movsx rcx, DWORD PTR [rdx] # load with sign-extension from int32 to int64
cmp ecx, 127
jle .L82 # conditional-jump over the next instruction
add rbp, rcx # sum+=data[c]
.L82:
add rdx, 4
cmp rbx, rdx
jne .L83
'
루프에는 두 개의 루프-이어지는 종속성 체인이 있습니다: 'sum'와 루프 카운터입니다. 'sum' 체인은 0 또는 1 사이클 길이이며, 루프 카운터는 항상 1 사이클입니다. 그러나 Sandybridge에서는 루프가 5개의 결합 도메인 uop이므로 반복당 1 사이클로 실행할 수 없으므로 지연 시간은 병목이 되지 않습니다.
아마도 한 번 반복이 2 사이클 정도로 실행될 것입니다 (분기 명령어 처리량에 병목이 생기기 때문에), 반면 -O3 루프의 경우에는 3 사이클마다 한 번 실행됩니다. 다음 병목은 ALU uop 처리량이 될 것입니다. 4 개의 ALU uop (not-taken 경우)이지만 ALU 포트는 3 개뿐입니다. (ADD는 모든 포트에서 실행할 수 있습니다).
이 파이프라인 분석 예측은 -O3에 대해 약 3초, -O2에 대해 약 2초로 정확히 일치합니다.
하스웰/스카이레이크는 한 주기당 1개의 not-taken 케이스를 실행할 수 있습니다.왜냐하면 taken 분기와 동일한 주기에 not-taken 분기를 실행할 수 있으며 4개의 ALU 포트를 가지고 있기 때문입니다. (또는 약간 적게 실행할 수도 있습니다. 'a 5 uop loop doesn't quite issue at 4 uops every cycle' ).
(방금 테스트한 결과: Skylake @ 3.9GHz는 전체 프로그램의 분기 포함 버전을 1.45초에 실행시키거나, 분기 없는 버전을 1.68초에 실행시킵니다. 그러므로 그 차이는 훨씬 작습니다.)
g++6.3.1은 #$^&**#$&을 사용하며, '-O2'에서도 여전히 그렇지만, g++5.4는 여전히 4.9.2와 같이 작동합니다.
g++6.3.1과 g++5.4 모두 #^@!^@$$& / '-fprofile-use'을 사용하면 '-O3' 에도 분기된 버전이 생성됩니다 ( '-fno-tree-vectorize' 상태에서도).
새로운 gcc의 CMOV 버전 루프는 CMP/CMOV 대신에 'add ecx,-128' / 'cmovge rbx,rdx'를 사용합니다. 그게 좀 이상하지만 아마도 속도를 늦추지는 않을 것입니다. ADD는 플래그와 함께 출력 레지스터에 쓰기 때문에 물리적 레지스터의 수에 대한 압력을 더 가중시킵니다. 그러나 병목 현상이 아닌 한 거의 동등할 것입니다.
새로운 gcc는 -O3 옵션으로 루프를 자동 벡터화한다. 이는 단지 SSE2만 사용해도 상당한 속도 향상을 가져온다. (예: 나의 i7-6700k Skylake는 벡터화된 버전을 0.74초에 실행하여 스칼라보다 약 두 배 빠르다. 또는 AVX2 256비트 벡터를 사용하여 0.35초에 실행된다).
벡터화된 버전은 많은 명령어처럼 보이지만 그렇게 나쁘지 않으며 대부분은 루프 기반 종속 체인의 일부가 아닙니다. 끝 부분에서만 64비트 요소로 언팩해야 합니다. 하지만 조건이 이미 모든 음수 정수를 제로로 만들었을 때는 부호 확장 대신 제로 확장할 수 있다는 것을 인지하지 못하여 두 번이나 'pcmpgtd'을 수행합니다.
답변 2
gcc의 최적화 플래그 -O3은 -O2보다 코드를 느리게 만듭니다. 이 주제에 대해 SEO에 의식한 한국어 에세이를 작성해 보았습니다.안녕하세요. 오늘은 GCC 컴파일러의 최적화 플래그 -O3와 -O2의 성능 차이에 대해 알아보려고 합니다. 이러한 주제는 많은 프로그래머들에게 영향을 미치는 중요한 주제 중 하나입니다.
최적화 플래그는 컴파일러가 소스 코드를 더 빠르고 효율적으로 실행할 수 있도록 도와주는 매개 변수입니다. GCC에서도 -O2와 -O3은 가장 많이 사용되는 최적화 수준 중 일부입니다.
-O2는 GCC의 기본 최적화 수준이며, 대부분의 경우에 좋은 퍼포먼스를 보여줍니다. 이 최적화 수준은 실행 시간을 최적화하는데 중점을 두고 있습니다. 그러나 -O3은 -O2보다 높은 최적화 수준으로 간주됩니다. -O3은 코드의 실행 속도를 더욱 빠르게 만들기 위해 코드의 크기와 복잡성을 최적화하는데 중점을 둡니다.
이제 여기서 중요한 관점을 살펴보겠습니다. -O3을 사용하면 코드 크기가 더욱 커질 수 있으며, 코드 최적화 작업이 더욱 복잡해질 수 있습니다. 이는 일부 특정한 상황에서 -O3이 -O2보다 실행 시간이 더 길어지는 이유가 될 수 있습니다.
또 다른 이유는 -O3이 코드를 병렬로 실행하도록 자동으로 최적화하기 때문입니다. 모든 코드가 병렬로 실행될 수 있는 것은 아니며, 코드간 종속성이나 메모리 접근 패턴 등 다양한 요인들에 따라 최적화 수준의 선택이 달라질 수 있습니다.
따라서 최적화 플래그를 선택할 때는 성능 향상에 집중하는 것이 아니라, 실제 적용할 코드에 맞는 최적화 수준을 선택해야 합니다. -O3보다 -O2가 더 나은 성능을 보이는 경우도 있을 수 있습니다.
마지막으로 언급할 점은 퍼포먼스 개선을 위해 최적화 플래그를 과도하게 사용하는 것은 오히려 부작용을 초래할 수 있다는 것입니다. 때때로 최적화가 잘못된 방향으로 적용될 수 있기 때문에 주의가 필요합니다.
결론적으로, GCC 컴파일러의 최적화 플래그 -O3과 -O2의 성능 차이는 매우 상황적입니다. 코드의 크기와 복잡성, 병렬 실행 가능성 등 다양한 요소를 고려하여 최적화 수준을 선택해야 합니다. 최적화 플래그의 사용은 성능 향상을 위해 명확한 이점을 가져다주지 않을 수도 있다는 것을 기억해야 합니다.