핵심 요약
- 자작 C 컴파일러(antcc)에서 Lua 벤치마크 시 -O2를 적용하면 오히려 기본 빌드(1.048초)보다 느린 1.410초를 기록하는 역최적화가 발생했습니다.
- 인라인 최적화로 인해 중간 표현(IR)은 11%만 늘어났지만, 레지스터 압박과 스택 스필링이 폭증하며 전체 명령어의 40% 이상이 스택 이동 연산으로 채워진 것이 원인이었습니다.
요약 자신이 직접 C 컴파일러(antcc)를 개발 중인 한 개발자가 간단한 Lua 벤치마크를 돌리다 황당한 역최적화(pessimization) 현상을 마주하고 원인을 추적한 개발기입니다.
벤치마크 결과, 최적화 옵션을 전혀 주지 않은 기본 빌드는 1.048초가 걸린 반면, -O1은 1.077초, -O2는 무려 1.410초나 걸렸습니다. 개발자의 컴파일러는 아직 구현된 최적화 기능이 많지 않아 속도가 극적으로 빨라지진 않더라도, 최적화 레벨을 올렸더니 1.4배나 느려지는 것은 명백한 버그였습니다.
antcc에 구현된 핵심 최적화 중 하나는 인라인(inlining) 처리였는데, 인라인을 끄면 역최적화가 사라지는 현상이 관찰되었습니다. 조사 결과 핵심 원인은 Lua VM의 메인 바이트코드 인터프리터 루프인 `luaV_execute` 함수에 있었습니다. -O2 적용 시 이 함수의 코드 크기가 -O1 대비 약 2.4배나 급증해 약 43KB까지 불어났습니다.
흥미롭게도 최종 바이너리로 변환되기 전 단계인 중간 표현(IR)을 확인해 보니, -O2의 IR 크기는 -O1보다 겨우 11% 컸을 뿐이었습니다. 11%의 IR 차이가 최종 머신 코드에서 2.4배 차이로 뻥튀기된 범인을 추적하기 위해 명령어 선택(instruction selection)과 레지스터 할당(register allocation) 단계를 검토했습니다.
확인 결과 레지스터 할당이 주원인으로 드러났습니다. antcc는 기본적인 선형 스캔(linear scan) 레지스터 할당 방식을 사용하고 있었는데, 인라인화 과정에서 레지스터 압박(register pressure)이 극심해지면서 레지스터가 부족해져 스택으로 변수를 내쫓는 스필링(spilling)이 대량 발생했습니다. 이로 인해 -O2 적용 후 `luaV_execute` 디스어셈블리 코드의 40% 이상이 레지스터와 스택 사이에서 값을 셔플하는 `mov` 명령어로 가득 차 버렸고, 레지스터 할당 전 IR에 수많은 괴상한 phi 노드들이 생성되어 있었던 것이 문제의 핵심 신호로 포착되었습니다.
Sponsored · 광고