- 간단한 정수 합산 함수를 컴파일할 때 GCC와 Clang의 최적화 차이를 관찰한 사례
- GCC는 루프 내에서 두 수를 한 번에 더하는 x*2 + 1 형태의 루프 최적화를 수행
- Clang은 루프를 완전히 제거하고, v(v - 1)/2라는 닫힌 형태의 수식으로 계산을 대체
- 이 변환으로 코드가 O(n)에서 O(1) 로 바뀌며, 수학적 단순화가 자동으로 이루어짐
- 20년 넘게 컴파일러를 다뤄온 저자도 놀랄 만큼, 컴파일러의 지능적 최적화 수준을 보여주는 사례
GCC의 루프 최적화
- 단순한 정수 합산 함수를 작성했을 때 GCC는 루프 기반의 효율적 합산 코드를 생성
- 루프 내부에서 lea edx, [rdx+1+rax*2] 명령을 사용해 두 수를 동시에 더함
- 이는 x와 x+1을 더하는 연산을 x*2 + 1로 변환한 형태
- 최적화 레벨을 -O3로 높이면 병렬 덧셈(vectorization) 을 통해 루프를 더 빠르게 처리
- 이러한 방식은 반복문을 유지하면서도 연산 효율을 극대화하는 전통적 최적화 형태
Clang의 수학적 최적화
- 동일한 코드를 Clang으로 컴파일하면 루프가 완전히 사라짐
- Clang은 입력값이 양수인지 확인한 뒤, 일련의 lea, imul, shr 명령으로 계산 수행
- 결과적으로 (v² - v)/2, 즉 정수 합의 닫힌 형태 수식으로 변환
- 이 변환은 반복문을 제거하고 상수 시간(O(1)) 계산으로 대체
- 저자는 이 결과를 “정수 합의 수학적 해법을 스스로 찾아낸 것”이라 표현
수식 전개 과정
- Clang이 생성한 어셈블리 코드를 수학적으로 역추적하면 다음과 같은 변환이 이루어짐
-
v + ((v - 1)(v - 2) / 2) - 1
- 이를 전개하고 정리하면 (v² - v)/2로 단순화
- 최종적으로 v(v - 1)/2 형태가 되어, 1부터 v까지의 합 공식과 일치
- 이 과정은 컴파일러가 수학적 패턴을 인식하고 최적화한 사례로 제시됨
컴파일러의 지능적 동작
- Clang이 이 특정 명령 시퀀스를 사용하는 이유는 오버플로 방지와 유도 변수 추적 방식 때문으로 설명
- 정확한 내부 동작 원인은 명확하지 않지만, Clang의 내부 최적화 로직이 복합적으로 작용한 결과로 언급
- 저자는 이러한 결과를 “겸허하고 영감을 주는 경험”으로 표현
마무리 및 시리즈 맥락
- 이 글은 ‘Advent of Compiler Optimisations 2025’ 시리즈의 24번째 글
- 컴파일러가 코드 변환을 통해 수학적 단순화와 성능 향상을 달성하는 과정을 탐구
- 저자는 “컴파일러는 여전히 나를 놀라게 한다”며, 오랜 경험에도 불구하고 지속되는 기술적 경이로움을 강조