컨티뉴는 계속될 것이다

[JA] Continuation is to be continued / Masayuki Mizuno @fetburner

작성자
RubyKaigi
발행일
2025년 05월 27일

핵심 요약

  • 1 Ruby의 `callcc`는 현재의 실행 흐름(컨티뉴에이션)을 일급 값으로 다루어 비결정 계산을 위한 DSL 구현에 유용하지만, 전체 컨티뉴에이션을 캡처하고 성능이 저하되는 문제가 있습니다.
  • 2 `shift/reset`은 `callcc`와 달리 제한된 범위의 컨티뉴에이션만 캡처하여 DSL 구현을 간소화하고, `callcc`를 기반으로 구현하더라도 성능 개선을 이룰 수 있습니다.
  • 3 `shift/reset`을 C 확장 라이브러리로 VM 상태를 직접 조작하여 구현하면 스택 관리 최적화를 통해 `callcc`의 성능 문제를 근본적으로 해결하고 더욱 높은 효율성을 달성할 수 있습니다.

도입

루비 개발자인 미즈노는 함수형 언어에서 자주 사용되는 '컨티뉴에이션 오퍼레이터'가 루비에도 구현되어 있음에 주목하며 발표를 시작합니다. 발표는 컨티뉴에이션의 개념 설명부터 시작하여, 루비의 `callcc`가 비결정 계산 DSL 구현에 어떻게 활용될 수 있는지 보여줍니다. 그러나 `callcc`의 여러 문제점, 특히 성능 및 사용상의 복잡성을 지적하며, 이를 해결할 대안으로 'shift/reset' 컨티뉴에이션 오퍼레이터를 소개하는 것이 이 발표의 주요 목적입니다.

컨티뉴에이션 및 callcc의 이해

컨티뉴에이션은 현재 평가 중인 식의 값을 받은 후 이어질 계산 전체를 의미하며, 루비의 callcc는 이를 일급 값으로 캡처하고 재활용할 수 있게 합니다. 그러나 callcc는 Ruby 2.2.2부터 Deprecated되었으며, 파이버(Fiber)와 달리 캡처된 컨티뉴에이션을 여러 번 호출할 수 있는 특성을 가집니다.

callcc를 활용한 DSL 구현 및 한계

발표자는 하스켈(Haskell)의 리스트 모나드(List Monad)와 유사하게 백트래킹(backtracking)을 평탄하게 기술할 수 있는 DSL 구현 예시를 통해 callcc의 유용성을 입증합니다. 일반적인 메타 프로그래밍 방식의 DSL 구현은 새로운 구문 추가 시 DSL 구현부를 계속 수정해야 하고 변수 관리가 복잡해지는 문제가 있습니다. 반면, callcc를 사용하면 루비의 변수를 그대로 활용하며 백트래킹을 자연스럽게 구현할 수 있어 DSL 개발의 편의성이 크게 향상됩니다.

callcc의 문제점

그럼에도 불구하고 callcc는 다음과 같은 심각한 문제점을 내포합니다. * 과도한 컨티뉴에이션 캡처: 필요한 부분뿐만 아니라 현재 실행 중인 전체 컨티뉴에이션을 캡처하여 불필요한 코드가 포함되고, callcc 호출 후 수동으로 복귀 지점을 지정해야 하는 복잡성을 야기합니다. * 극심한 성능 저하: callcc는 VM 내부 구현과 밀접하게 연관되어 있으며, 스택(stack) 전체를 memcpy로 복사하고 setjmp/longjmp로 실행 위치를 기록/복원하는 과정에서 발생하는 오버헤드로 인해 성능이 매우 나쁩니다. 부분 스택 백업(sub-backup) 최적화가 적용되어 있지만, 여전히 비효율적입니다.

shift/reset을 통한 문제 해결

shift/resetshift부터 reset까지의 제한된 범위 내 컨티뉴에이션만 캡처하는 오퍼레이터입니다. 이는 DSL 구현을 훨씬 간결하게 만들며, callcc와 달리 reset 지점으로 자동으로 복귀하므로 캡처된 컨티뉴에이션을 함수처럼 인수로 전달하여 사용할 수 있습니다. 놀랍게도 shift/resetcallcc를 사용하여 구현할 수 있으며, 이러한 방식으로 구현된 shift/reset을 DSL에 적용했을 때 callcc 단독 사용 시보다 성능이 크게 개선됨을 확인했습니다. 이는 shift/reset의 구조가 callcc의 스택 최적화와 더 잘 부합하기 때문으로 분석됩니다.

결론

`callcc`는 비록 Deprecated되었지만, 특정 DSL 구현에서 강력한 잠재력을 발휘합니다. 그러나 과도한 컨티뉴에이션 캡처와 심각한 성능 저하라는 고질적인 문제를 안고 있습니다. 이러한 `callcc`의 한계는 `shift/reset`이라는 제한된 컨티뉴에이션 오퍼레이터를 통해 효과적으로 해결될 수 있음을 확인했습니다. `callcc` 기반 `shift/reset` 구현만으로도 DSL 개발의 용이성과 성능 개선을 달성할 수 있었으며, 향후 `shift/reset`을 C 확장 라이브러리로 루비 VM 내부에 직접 구현한다면 스택 관리 최적화를 통해 훨씬 더 높은 실행 효율성을 확보할 수 있을 것으로 기대됩니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!