
개발을 하다 보면 이런 생각이 한 번쯤 든다.
진짜 끝까지 최적화하려면
주석도 없애고, 파일도 전부 하나로 합치는 게 제일 빠른 거 아닐까?
직관적으로는 맞는 것처럼 보인다.
쓸데없는 걸 다 덜어내고, 코드도 한 덩어리로 만들면 프로그램이 더 가벼워질 것 같기 때문이다.
하지만 실제로는 대부분의 경우 그렇지 않다.
주석을 지우거나 파일을 하나로 합치는 것은 보통 실행 성능과 거의 관계가 없다.
다만 완전히 틀린 생각도 아니다.
특정 상황에서는 실제로 이득이 생길 수도 있다.
이 글에서는 그 차이를 설명해보려 한다.
먼저 주석부터 보자.
대부분의 컴파일 언어에서 주석은
컴파일 단계에서 제거
된다.
예를 들어 이런 코드가 있다.
int add(int a, int b) {
// 두 수를 더한다
return a + b;
}
그리고 주석을 지운 코드
int add(int a, int b) {
return a + b;
}
이 두 코드는 보통
실행 결과
+
생성되는 기계어
가 동일하다.
즉 주석은 실행 성능에 영향을 주지 않는다.
주석이 영향을 줄 수 있는 것은 보통 이런 것들이다.
즉 주석 제거는
런타임 최적화 ❌
소스 관리 최적화 ⭕
에 가깝다.
그럼 파일을 하나로 합치면 어떨까?
많은 사람들이 이렇게 생각한다.
파일 많음 → 느림
파일 하나 → 빠름
하지만 실제 성능은 파일 개수보다
컴파일러 최적화
메모리 접근
알고리즘
같은 요소에 훨씬 크게 좌우된다.
즉 중요한 질문은 이것이다.
파일이 몇 개인가가 아니라
컴파일러가 코드 전체를 얼마나 잘 이해하느냐
이다.
이 생각이 완전히 틀린 건 아니다.
옛날 컴파일러에서는
파일 단위 컴파일
이 일반적이었다.
그래서 다른 파일에 있는 함수는
같은 최적화가 덜 되는 경우가 있었다.
그래서 옛날에는
한 파일
이 실제로 조금 더 빠른 경우도 있었다.
하지만 현대 컴파일러는 상황이 다르다.
예
LTO
ThinLTO
Whole program optimization
PGO
같은 기술 덕분에
파일이 나뉘어 있어도 전체 프로그램을 보고 최적화한다.
실제로 프로그램이 느린 이유는 보통 이런 것들이다.
즉 프로그램이 느린 이유는
파일이 많아서
가 아니라
핫패스에서 비싼 일을 많이 해서
인 경우가 대부분이다.
그렇다고 이 아이디어가 완전히 틀린 건 아니다.
특정 상황에서는 실제 이득이 있다.
Python / JavaScript 같은 환경에서는
import
module resolution
디스크 접근
이 비용이 존재한다.
이 경우
번들링
이 도움이 될 수 있다.
메모리나 바이너리 크기가 매우 중요한 환경에서는
코드 통합
주석 제거
같은 작업이 실제 의미가 있다.
웹 프론트엔드에서는
minify
bundle
tree shaking
같은 방식으로
코드 크기
를 줄인다.
이건 실행 속도보다
네트워크 최적화
에 가깝다.
대부분의 프로젝트에서는 오히려 단점이 더 크다.
파일이 잘 나뉘어 있으면
한 파일만 다시 컴파일
하면 된다.
하지만 거대한 단일 파일이면
전체 컴파일
이 필요할 수 있다.
파일이 많으면 컴파일을 병렬로 할 수 있다.
단일 파일이면
빌드 병렬성 감소
가 생긴다.
거대한 파일은
을 만든다.
성능 최적화는 보통 이렇게 진행된다.
profiling
benchmark
느린 1%
을 찾는다.
예
O(n²)
→
O(n log n)
예
LTO
PGO
inline optimization
한 문장으로 정리하면 이렇다.
극한의 최적화는 모든 걸 하나로 합치는 것이 아니라
정확한 병목을 찾아서 그 부분만 개선하는 것이다.
즉
주석 제거 = 거의 의미 없음
파일 하나 = 일부 상황에서만 의미 있음
대부분의 경우 성능은
알고리즘
메모리
I/O
컴파일러 최적화
에서 나온다.
그래서 진짜 잘하는 최적화는
코드를 전부 합치는 것이 아니라
어디를 최적화해야 하는지 정확히 아는 것이다.