메뉴
HN
Hacker News • 47일 전

C언어의 꼬리 호출 최적화(TCO)는 비교적 최근 도입되었습니다.

IMP
6/10
핵심 요약

C언어의 전통적인 호출 규약(Call Convention) 한계로 인해 꼬리 호출 최적화(TCO)는 오랫동안 제한적으로 적용되었으나, 최근 GCC와 Clang 컴파일러에서 제대로 지원되기 시작했습니다. 이를 통해 인터프리터 디스패치나 방대한 코드 조각을 사용하는 고성능 컴파일러 구현 시 성능을 크게 끌어올릴 수 있다는 점에서 개발자에게 중요한 변화입니다.

번역된 본문

제목: C언어의 꼬리 호출 최적화(Tail-call optimization)는 비교적 최근의 일입니다. 게시일: 2025년 8월 21일 (목) 22:11 UTC, 작성자: anton 상위 기사: 파이썬(Python), 꼬리 호출(tail calls), 그리고 성능

사실 C언어에서의 꼬리 호출은 아주 예전부터 존재했던 것은 아닙니다. C언어의 호출 규약(Call convention)에 따르면, 피호출자(callee)는 호출자(caller)가 스택에 밀어 넣은 어떤 것도 제거하지 않는 것으로 되어 있었습니다. 호출자는 int f(); 와 같은 선언을 볼 수 있으며, 실제 호출은 n>0개의 인수를 가질 수 있고, 실제 함수는 m≤n개의 매개변수를 가질 수 있습니다. 피호출자가 인수를 제거한다면 이것이 항상 작동하지는 않을 것입니다. 따라서 호출자는 호출(call)과 다음 반환(return) 사이에 인수를 제거해야 했고, 이로 인해 해당 호출은 꼬리 호출이 아닌 일반 호출이 되었습니다.

제가 1994년 당시의 C 컴파일러들을 살펴보았을 때, 그것들은 기사에 나온 것과 같은 방식의 사용례에 대해 꼬리 호출 최적화를 수행하지 않았습니다. 2001년에 Mark Probst는 별도의 호출 규약을 사용하여 GCC에 꼬리 호출 최적화를 구현했습니다. 그는 6.4절에서 당시 GCC의 꼬리 호출 최적화가 가진 한계를 나열했는데, 여기에는 "간접 호출(indirect calls)을 처리할 수 없다"(이는 인터프리터 디스패치를 위한 꼬리 호출에서 사용되었을 것입니다)와 같은 내용이 포함되어 있었습니다. 그 이후로 저는 이 문제를 따로 살펴보지 않았습니다(GCC의 goto * 기능이 충분히 좋았기 때문입니다). 또한 GCC의 꼬리 호출 지원과 관련하여 무언가가 변경되었을 것이라고 가정할 만한 이유도 별로 없었습니다(비록 한 릴리스 노트에 형제 호출(sibcalls)이 언급되었고, 그것을 한번 확인해 봐야겠다고 생각했던 기억은 있지만 말입니다).

어쨌든, 작년에 저는 Xu와 Kjolstad가 작성한 "Copy-and-Patch 컴파일"에 관한 논문을 읽었고, 그들은 여기에 꼬리 호출 최적화를 사용합니다. 어쨌든 그 논문을 읽은 후, 저는 GCC와 Clang이 기사에 나온 종류의 꼬리 호출에 대해 꼬리 호출 최적화를 수행할 수 있는지 몇 가지 테스트를 해보았습니다. 그리고 그것이 작동했습니다. Xu와 Kjolstad는 자신들이 10만 개의 코드 스니펫을 사용한다고 보고한 반면, 우리는 Gforth에서 이를 2,000개 미만으로 제한합니다(VM 명령어, 스택 캐싱 변형, 정적 슈퍼명령어 등). 10만 개를 사용할 수 있게 된다면, goto * 기반 시스템에서는 너무 많은 서로 다른 코드 스니펫이 필요해 사용할 수 없었던 기술들을 활용할 수 있게 해줄 것입니다. 우리는 아직 이것을 Gforth에 적용할 여유가 없었으므로, 이를 먼저 성공해 낸 파이썬 커뮤니티를 축하합니다.

댓글 기란가? 최근에 저는 명령어 디스패치를 위해 꼬리 호출을 사용하는 Forth 변형 (토이) 인터프리터를 구현했습니다. 제가 얻은 주요 이점은 '내장 함수 호출'이라는 별도의 명령어를 두는 대신, 모든 내장 함수가 스스로 명령어처럼 동작하게 만들 수 있었다는 점입니다. 또한 소수의 슈퍼명령어(super-instructions)도 지원하는데, 이들은 다른 것들과 크게 다르지 않기 때문에 성능 향상에 꽤 도움이 되었습니다.

성능은 꽤 괜찮은 편이지만, 제가 원하는 수준에 도달하려면 옵티마이저에서 약간의 작업이 더 필요합니다. 궁극적으로 저는 이것을 소형 컴퓨터에서 실행하고, 브라우저를 통해 인터프리터를 노출하며, 사용자가 LED 매트릭스를 제어하는 코드를 작성할 수 있게 하고 싶습니다. 이 프로젝트(https://www.noisebridge.net/wiki/Flaschen_Taschen)와 비슷하지만 더 작은 규모라고 생각하시면 됩니다. 코드는 여기(https://github.com/lpereira/lwan/blob/master/src/samples/...)에 있으며, 이는 Forth Haiku 언어의 변형으로, ShaderToy가 GLSL을 위해 그러하듯 작은 Forth 코드 조각으로 예술 작품을 만들 수 있게 해줍니다.

원문 보기
원문 보기 (영어)
Tail-call optimization in C is relatively recent Posted Aug 21, 2025 22:11 UTC (Thu) by anton (subscriber, #25547) Parent article: Python, tail calls, and performance Actually tail calls in C have not been around forever. The C calling convention has been that the callee does not remove any stuff the caller has put on the stack. The caller could see the declaration int f(); , the actual call could have n>0 arguments, and the actual function could have m&leq;n parameters. That would not always work if the callee removed the arguments. So the caller had to remove the arguments between the call and the following return, turning the call into a non-tail call. When I looked in 1994 at the C compilers of the day, they did not perform tail-call optimization for the kind of usage shown in the article. In 2001 Mark Probst implemented tail-call optimization in GCC with a separate calling convention; he lists the limitations of the then-existing tail-call optimization in GCC in section 6.4, among them: "It cannot handle indirect calls" (which would have been used in tail calls for interpreter dispatch). I have not looked at the issue since then (GCC's goto * was good enough (well, mostly)), and I had not much reason for assuming that something had changed wrt to GCC support for tail-calls (although one release note mentioned sibcalls, and I remember thinking that I should be checking that out. Anyway, last year I read the paper on "Copy-and-Patch Compilation" by Xu and Kjolstad, and they use tail-call optimization. In any case, after reading that paper, I made some tests if gcc and clang can do tail-call optimization for the kind of tail calls shown in the article. And it works. And Xu and Kjolstad report that they use 100,000 code snippets, whereas we limit ourselves in Gforth to <2000 (for VM instructions, stack caching variations thereof, static superinstructions etc.). Being able to do 100,000 would allow us to use techniques that need too many different code snippets to be usable in a goto * -based system. We have not gotten around to putting this into Gforth yet, so congratulations to the Python community for being there first. to post comments Tail-call optimization in C is relatively recent Posted Aug 23, 2025 19:43 UTC (Sat) by lafp (subscriber, #89554) [ Link ] For what it's worth, I recently implemented a (toy) interpreter for a variant of Forth that uses tail calls for dispatching instructions. The main gain I had was making all the built-in functions behave like instructions themselves, rather than having a "call a built-in function" instruction; it also has support for a few handfuls of super-instructions, which helped quite a bit as they're not that different than anything else. The performance is pretty decent, but it'll need a bit more work in the optimizer to reach what I need it to reach (ultimately I want this to run on a small computer, expose the interpreter through a browser, and let people write code that's then used to control a LED matrix; think of this project, but in a smaller scale: https://www.noisebridge.net/wiki/Flaschen_Taschen ). The code is here ( https://github.com/lpereira/lwan/blob/master/src/samples/... ) and it's a variant of the Forth Haiku language, that lets you create art with small bits and pieces of Forth code, not unlike ShaderToy is for GLSL.