**인라이닝(inlining)**은 함수 호출을 호출부에 코드를 펼쳐 넣는 최적화입니다. µs 단위 최적화에서 핫 루프 안에 함수 호출이 남아 있으면, 그것은 매 반복마다 push/pop, 스택 프레임 설정, 반환 주소 저장 등의 고정 비용이 더해진다는 의미입니다. 이 비용은 함수 본체가 작을수록 상대적으로 큽니다. 예를 들어 1 nanosecond 짜리 연산을 하는 함수를 호출할 때, 함수 호출 오버헤드가 2~5 ns라면 본체보다 오버헤드가 더 큰 상황입니다.
인라이닝은 컴파일러가 자동으로 하는 것이 원칙이지만, 특정 조건이 맞지 않으면 인라인되지 않습니다. 이 챕터에서는 컴파일러 관점에서 실패 원인을 진단하고, 리포트와 어셈블리로 직접 확인하는 방법을 다룹니다.
인라이닝 실패의 주요 원인
컴파일러가 함수 호출을 인라인하지 않고 그대로 두는 이유는 크게 세 가지로 나눌 수 있습니다.
가시성(다른 번역 단위)
일반 컴파일 모델에서는 각 TU가 독립적으로 컴파일됩니다. 호출하는 쪽 TU에는 “이 함수의 몸체"가 보이지 않고, 선언만 보이는 경우가 많습니다. 컴파일러는 다른 오브젝트에 있는 구현을 볼 수 없으므로, 그 경계를 넘는 크로스-TU 인라이닝을 하지 못합니다. 헤더에 정의가 있는 인라인 함수나 템플릿 인스턴스는 호출하는 TU에서 본문이 보이므로 인라인 후보가 됩니다. 따라서 핫 경로에 있는 작은 함수는 호출하는 TU에서 정의가 보이도록 헤더에 두거나, 같은 .cc에 두는 것이 유리합니다.
ODR/ABI
ODR(One Definition Rule) 위반이 있거나, 서로 다른 컴파일 옵션·ABI로 컴파일된 코드가 섞이면, 컴파일러가 “같은 함수"로 확신하지 못해 인라인을 포기할 수 있습니다. 공유 라이브러리 경계(DLL/SO)를 넘는 호출은 보통 인라인되지 않고, 호출 규약에 따른 호출 비용이 남습니다. 헤더 전용 라이브러리나 LTO를 쓰면 이런 경계가 완화됩니다.
코드 크기 한도
인라이닝을 무한히 하면 코드 크기가 커져 I-cache 압박과 컴파일 시간이 늘어납니다. 컴파일러는 내부 휴리스틱(함수 크기, 호출 횟수, 루프 내부 여부 등)으로 “인라인할 가치가 있는지"를 판단하고, 한도(-finline-limit 등)를 넘으면 인라인을 하지 않습니다. 작고 자주 호출되는 함수일수록 인라인되기 쉽고, 크고 복잡한 함수는 호출로 남는 경우가 많습니다.
인라이닝 리포트 확인 방법
컴파일러가 인라이닝 결정 과정을 리포트로 출력하도록 할 수 있습니다. 이를 통해 “어떤 함수가 인라인됐는지”, “왜 안 됐는지"를 텍스트로 확인할 수 있습니다.
GCC: -fopt-info-inline
| |
출력 예시:
| |
Inlining add (a call from compute)—compute에서add를 인라인했다는 메시지not inlined ... limit reached— 코드 크기 한도 초과로 인라인 거부
더 자세한 정보가 필요하면 -fopt-info-all 또는 -fopt-info-optimized로 전체 최적화 리포트를 볼 수 있습니다.
Clang: -Rpass=inline
| |
출력 예시:
| |
cost=-30, threshold=225— cost가 threshold보다 낮으면 인라인됩니다. cost가 높으면(함수가 크면) 인라인을 거부합니다.-Rpass-missed=inline으로 실패 케이스만 필터링하면 노이즈가 줄어듭니다.
어셈블리로 최종 확인
리포트와 함께 어셈블리에서 직접 call 유무를 확인합니다. 챕터 06의 방법대로 -S로 어셈블리를 뽑아서, 해당 호출 지점에 call _Z...가 남아 있으면 인라인되지 않은 것이고, 인라인되면 호출이 사라지고 해당 함수 본문이 펼쳐져 들어가 있습니다.
LTO를 켜면 링크 시점에 인라이닝이 일어나므로, 단일 TU 어셈블리만 보면 “인라인 안 됨"처럼 보이더라도 최종 바이너리에서는 인라인되었을 수 있습니다. LTO 사용 시에는 LTO 최적화 리포트(GCC -flto-report, Clang의 적절한 플래그)나 최종 디스어셈블리를 확인하는 것이 정확합니다.
inline / attribute / LTO가 가시성에 미치는 영향
- inline: C++에서
inline은 “여러 TU에 정의가 있어도 ODR만 지키면 된다"는 의미에 가깝고, “반드시 인라인해라"는 힌트가 아닙니다. 다만 인라인 후보로 고려되기는 하고, 헤더에 정의를 두면 모든 TU에서 본문이 보이므로 인라인 기회가 생깁니다. - attribute((always_inline)): GCC/Clang에서 “가능한 한 인라인해라"는 강한 힌트를 줍니다. 크기가 커져도 인라인을 시도하므로, 정말 작고 핫한 함수에만 쓰는 것이 좋습니다.
- LTO: 링크 시점에 모든 TU의 IR이 있으므로 가시성 제약이 사라집니다. 다른 오브젝트에 있는 함수도 인라인할 수 있어, “다른 .cc에만 있는 작은 함수"가 LTO에서 비로소 인라인되는 경우가 많습니다.
가능하면 핫 경로는 같은 TU에 두거나 헤더 인라인 + LTO로 정리하고, 인라이닝 리포트로 실제로 인라인되었는지 확인하는 흐름을 권장합니다.
실전 시나리오: 인라이닝이 안 될 때 점검 순서
- 가시성: 해당 함수 정의가 호출하는 TU에서 보이는가? 헤더에 인라인 정의가 있거나, 같은 .cc에 있으면 인라인 후보가 됨. 다른 .cc에만 있으면 LTO를 켜거나 해당 함수를 헤더/같은 TU로 옮긴다.
- 리포트 확인: GCC -fopt-info-inline, Clang -Rpass=inline으로 인라인되지 않은 이유(크기 한도·가시성 등)를 확인한다.
- 어셈블리: -S로 해당 호출 지점에
call이 남아 있는지 본다. LTO를 켠 경우에는 최종 바이너리 또는 LTO 단계 산출물을 확인한다. - 코드 구조 변경: Tr.02의 인라이닝 유도(헤더 인라인·final·같은 TU 배치)를 적용한 뒤 다시 2~3을 확인한다.
한눈에 보기: 인라이닝 실패 원인
| 원인 | 설명 | 대처 |
|---|---|---|
| 가시성 | 다른 TU에 구현만 있고 호출 TU에 본문이 안 보임 | 같은 TU·헤더 인라인·LTO |
| ODR/ABI | 정의 불일치·DLL/SO 경계 | 헤더 전용·LTO·ABI 통일 |
| 코드 크기 | 휴리스틱 한도 초과 | 함수 축소·always_inline(주의) |
판단 기준: 언제 진단할지
| 상황 | 권장 | 비권장 |
|---|---|---|
| 핫 경로에 호출 비용 의심 | 인라이닝 리포트·어셈블리 확인 | 추측만 하고 코드 변경 |
| LTO 켠 뒤에도 인라인 안 됨 | ODR·크기 한도·리포트 확인 | LTO만 의존 |
| 인라인 유도 후 검증 | -fopt-info-inline 등으로 재확인 | 변경 후 측정 생략 |
자주 하는 실수
- always_inline 과다 사용:
__attribute__((always_inline))를 여러 함수에 걸면 코드 크기가 불어나 I-cache 압박과 컴파일 시간이 늘어난다. 정말 작고 핫한 함수 한두 개에만 쓰고, 나머지는 컴파일러 휴리스틱에 맡기는 것이 좋다. - LTO만 믿고 가시성 무시: LTO가 크로스-TU 인라이닝을 해 주지만, ODR 위반·ABI 불일치·크기 한도에 걸리면 인라인되지 않을 수 있다. 인라인이 안 될 때는 리포트와 어셈블리로 원인을 확인한 뒤 코드 구조(같은 TU·헤더 인라인)를 보완한다.
- 변경 후 측정 생략: 헤더로 옮기거나 LTO를 켠 뒤 “이제 인라인되겠지"라고 가정하지 말고, -fopt-info-inline 또는 -Rpass=inline으로 실제로 인라인되었는지 확인하고, 필요하면 -S로 호출이 사라졌는지 본다.
학습 성과 목표
- 인라이닝 실패의 가시성·ODR/ABI·코드 크기 원인을 설명하고, 각각에 대한 대처를 말할 수 있다.
- GCC -fopt-info-inline, Clang -Rpass=inline으로 인라이닝 리포트를 확인할 수 있다.
- Tr.02(인라이닝 유도)과 연계해, 진단 후 코드 구조·LTO로 인라이닝을 유도할 수 있다.
비판적 시각: 한계와 트레이드오프
인라이닝은 무조건 많을수록 좋은 것이 아니다. 과도한 인라이닝은 코드 크기·컴파일 시간을 늘리고 I-cache를 압박할 수 있다. 컴파일러는 크기 한도와 호출 빈도를 보고 “인라인할 가치"를 판단하므로, 항상_inline으로 강제하기보다는 핫 경로를 같은 TU·헤더에 두고 LTO로 기회를 주는 쪽을 우선한다. “인라인 안 됨 = 무조건 나쁨"이 아니라, 측정 후 병목이 호출 비용일 때만 집중적으로 유도하는 것이 합리적이다.
핵심 요약
| 항목 | 요약 |
|---|---|
| 실패 원인 | 가시성(다른 TU)·ODR/ABI·코드 크기 한도 |
| 진단 | -fopt-info-inline(GCC), -Rpass=inline(Clang), -S 어셈블리 |
| 대처 | 같은 TU·헤더 인라인·LTO·always_inline(선택) |
Tr.02(언어 최적화)과의 연계
Low-latency C++ 언어 최적화 트랙(Tr.02)에서는 인라이닝을 유도하는 코드 구조를 다룹니다. 예를 들어 작은 함수를 헤더에 두기, 템플릿으로 구현을 호출부에 노출하기, final로 devirtualization을 유도하기 등입니다. 이 챕터(컴파일러 트랙)는 “왜 인라인되지 않았는지"를 진단하는 쪽에 초점을 둡니다. 코드 구조를 바꾼 뒤에도 인라이닝이 안 되면, 위의 가시성·ODR·크기 한도를 리포트와 어셈블리로 확인해 보면 됩니다.
다음 장에서는
어셈블리 레벨 코드 생성 분석, -S·objdump·Godbolt 사용, 함수 경계·호출 규약·hot 함수 형태 해석을 다룹니다.
→ 코드 생성 분석 (챕터 06)
![Featured image of post [Compiler 03] 인라이닝 실패 진단](/post/compiler-optimization/inlining-diagnostics/wordcloud_hu_ed9430e102b814c6.webp)
![[Compiler 03] PGO 고급 워크플로우](/post/compiler-optimization/pgo-workflow-advanced/wordcloud_hu_d830eb9ddd44a5a6.webp)
![[Compiler 03] GCC vs Clang vs MSVC 최적화 차이](/post/compiler-optimization/compiler-comparison-gcc-clang-msvc/wordcloud_hu_f9e92dca98d719c3.webp)
![[Compiler 03] 인라이닝 실패 진단](/post/compiler-optimization/inlining-diagnostics/wordcloud_hu_cf70a482efcb51cc.webp)
![[Compiler 03] 어셈블리 레벨 코드 생성 분석](/post/compiler-optimization/code-generation-analysis-assembly/wordcloud_hu_63d6f8980bdcdf0f.webp)
![[Compiler 03] CPU 기능별 함수 다중 버전](/post/compiler-optimization/function-multiversioning-cpu/wordcloud_hu_f5001b04a04f9a62.webp)
![[Compiler 03] AutoFDO 워크플로우: 샘플링 기반 프로파일 최적화](/post/compiler-optimization/autofdo-workflow-sampling-based/wordcloud_hu_d305107584078a19.webp)
![[Compiler 03] C++20 Modules와 빌드·성능](/post/compiler-optimization/cpp20-modules-build-performance/wordcloud_hu_fb6a0090a25cd0b2.webp)
![[Compiler 03] Introduction: Low-latency 컴파일러·빌드 최적화](/post/compiler-optimization/getting-started-compiler-build-performance-tuning/wordcloud_hu_54fcb9b6f9bee677.webp)