함수 호출과 인라이닝이 성능에 미치는 영향을 간단한 덧셈과 문자열 공백 세기 예제로 측정하고, 언제 인라이닝이 유리한지 정리한다.
Daniel Lemire는 소프트웨어 성능 전문가입니다. 그는 전 세계 과학자 상위 2%에 속하며(Stanford/Elsevier 2025), GitHub에서 가장 많이 팔로우되는 개발자 상위 1000명 중 한 명입니다.
메뉴 및 위젯
비즈니스에 도움이 필요하신가요? 연락 주세요. 비공개 강연, 교육, 컨설팅을 제공하며, 후원 기반 오픈소스 프로젝트도 진행합니다.
저는 어떤 광고도 받지 않습니다. 다만, GitHub에서 제 오픈소스 작업을 후원할 수 있습니다.
Daniel Lemire는 2004년에 이 블로그를 시작했습니다. 현재 2,351개의 글과 16,166개의 승인된 댓글이 있습니다.
Daniel Lemire의 블로그는 기술 뉴스 큐레이션 플랫폼 Hacker News에서 가장 인기 있는 블로그 상위 50위 안에 듭니다.
12,500명 이상의 이메일 구독자와 함께하세요:
GitHub에서 팔로우하기: X에서 팔로우하기: @lemire 팔로우
(팔로워 30,000명 이상) 이 블로그는 텔레그램에서도 팔로우할 수 있습니다. 다음을 검색하세요:
아카이브

프로그래밍을 할 때 우리는 함수들을 이어 붙입니다. 함수 A가 함수 B를 호출합니다. 그리고 또 그 다음으로 이어집니다.
꼭 이런 방식으로 프로그래밍해야 하는 것은 아닙니다. 단 하나의 함수만으로 전체 프로그램을 작성할 수도 있습니다. 사소하지 않은 프로그램을 단 하나의 함수로만 작성해 보는 건 재미있는 연습이 될 수도 있겠죠… 다만 코드 작성은 AI에게 맡기는 게 좋습니다. 사람은 긴 함수에서는 금방 버거워지니까요.
컴파일러 최적화의 핵심 중 하나는 ‘인라이닝(inlining)’입니다. 컴파일러가 함수 정의를 가져와 호출 지점에 대체하려고 시도하는 것입니다. 개념적으로는 꽤 단순합니다. 다음 예시를 보겠습니다. 함수 add3가 함수 add를 호출합니다.
cint add(int x, int y) { return x + y; } int add3(int x, int y, int z) { return add(add(x, y), z); }
다음과 같이 호출을 수동으로 인라인할 수 있습니다.
cint add3(int x, int y, int z) { return x + y + z; }
함수 호출은 성능 관점에서 꽤 저렴하지만, 공짜는 아닙니다. 함수가 사소하지 않은 인자를 받는다면, 스택에 저장했다가 복원해야 할 수 있으므로 추가적인 로드(load)와 스토어(store)가 발생할 수 있습니다. 함수로 점프해야 하고, 끝에서 다시 돌아오기 위해 점프해야 합니다. 또한 시스템의 호출 규약과 사용하는 명령어 종류에 따라, 시작과 끝에 추가 명령어가 더 붙기도 합니다.
add 같은 충분히 단순한 함수는, 성능이 중요할 때는 언제나 인라인되어야 합니다. 구체적인 예를 살펴봅시다. 배열의 정수들을 합산해 보겠습니다.
cfor (int x : numbers) { sum = add(sum, x); }
저는 MacBook(M4 프로세서, LLVM)을 사용했습니다.
| 함수 | ns/정수 |
|---|---|
| 일반(regular) | 0.7 |
| 인라인(inline) | 0.03 |
와. 인라인 버전이 20배 이상 빠릅니다.
무슨 일이 일어나는지 살펴봅시다. ‘add’ 함수의 호출 지점은 함수 호출이 들어간 단순한 루프일 뿐입니다.
asmldr w1, [x19], #0x4 bl 0x100021740 ; add(int, int) cmp x19, x20 b.ne 0x100001368 ; <+28>
함수 자체는 가능한 한 저렴합니다. 명령어 두 개뿐입니다.
asmadd w0, w1, w0 ret
즉, 덧셈 한 번마다 6개의 명령어를 쓰고 있습니다. 덧셈당 약 3사이클이 듭니다.
그렇다면 인라인 함수는 어떨까요?
asmldp q4, q5, [x12, #-0x20] ldp q6, q7, [x12], #0x40 add.4s v0, v4, v0 add.4s v1, v5, v1 add.4s v2, v6, v2 add.4s v3, v7, v3 subs x13, x13, #0x10 b.ne 0x1000013fc ; <+104>
완전히 다릅니다. 컴파일러가 덧셈을 고급(SIMD) 명령어로 바꿔, 8개의 명령어로 16개 정수를 블록 단위로 처리합니다. 즉 정수 하나당 반 개의 명령어(기존 6개에서)로 줄었습니다. 명령어 수를 12배 줄인 셈입니다. 게다가 명령어 수가 줄어든 것뿐 아니라, 프로세서는 사이클당 더 많은 명령어를 리타이어(retire)할 수 있어 성능이 크게 향상됩니다.
인라이닝은 유지하되 컴파일러가 이런 화려한 명령어(SIMD)를 쓰지 못하도록 막으면 어떨까요? 그래도 상당한 성능 향상(약 10배 빠름)을 얻습니다.
| 함수 | ns/정수 |
|---|---|
| 일반(regular) | 0.7 |
| 인라인(inline) | 0.03 |
| 인라인 (SIMD 없음) | 0.07 |
좋습니다. 하지만 add 함수는 다소 극단적입니다. 항상 인라인되어야 한다는 걸 이미 알고 있죠. 그렇다면 공백 문자의 개수를 세는 함수처럼 덜 사소한 경우는 어떨까요?
csize_t count_spaces(std::string_view sv) { size_t count = 0; for (char c : sv) { if (c == ' ') ++count; } return count; }
문자열이 충분히 길다면, 함수 호출 오버헤드는 무시할 수 있을 것입니다.
문자 1000개짜리 문자열을 넘겨 봅시다.
| 함수 | ns/문자열 |
|---|---|
| 일반(regular) | 111 |
| 인라인(inline) | 115 |
인라인 버전은 더 빠르지 않을 뿐 아니라, 오히려 약간 더 느립니다. 이유는 잘 모르겠습니다.
그럼 짧은 문자열(예: 0~6자)을 사용하면 어떨까요? 이 경우 인라인 함수가 측정 가능한 정도로 더 빠릅니다.
| 함수 | ns/문자열 |
|---|---|
| 일반(regular) | 1.6 |
| 인라인(inline) | 1.0 |
요약:
참고: 소스 코드는 공개되어 있습니다.
Daniel Lemire, "The cost of a function call," in Daniel Lemire's blog, 2026년 2월 8일, https://lemire.me/blog/2026/02/08/the-cost-of-a-function-call/.
퀘벡 대학교(TELUQ) 컴퓨터 과학 교수. Daniel Lemire의 모든 글 보기
2026년 2월 8일에 게시 | 작성자 Daniel Lemire | 카테고리
이메일 주소는 공개되지 않습니다.
댓글 *
이름 *
이메일 *
웹사이트
Δ
이 블로그는 이메일로도 구독할 수 있습니다(비상업적, 광고 없음, 주간 이메일)
코드를 게시하려면 tohtml 같은 도구로 서식을 맞추는 것을 고려해 보세요.