Rust `become`으로 구현한 꼬리 재귀 인터프리터, 성능은?
Rust의 `become` 키워드를 활용하여 꼬리 재귀(Tail Call) 인터프리터를 구현하여 ARM64 환경에서 기존 어셈블리 코드보다 높은 성능을 달성함.
x86-64 환경에서는 코드 생성(Codegen) 문제로 인해 어셈블리 구현에 미치지 못하는 성능을 보였으며, WASM 환경에서는 더욱 저조한 성능을 기록함.
Drop 구현(Drop Implementation)으로 인해 `become` 사용에 제약이 발생하며, Rust의 꼬리 재귀 최적화에 대한 추가적인 개선이 필요하다는 의견이 제기됨.
Rust `become`을 활용한 꼬리 재귀 최적화
게시물에서는 Rust의 `become` 키워드를 사용하여 꼬리 재귀 호출을 구현하고, 이를 통해 Uxn CPU 에뮬레이터를 개발했다. 특히, 함수 인자(Function Arguments)를 통해 VM 상태를 레지스터에 저장하고, 각 연산의 마지막에 다음 함수를 호출하는 방식으로 성능을 개선했다. 이러한 접근 방식은 ARM64 환경에서 기존 어셈블리 구현보다 더 나은 성능을 보였다.
x86-64 환경에서의 코드 생성(Codegen) 문제
x86-64 환경에서, Rust 컴파일러는 꼬리 재귀 호출 시 스택 프레임(Stack Frame)을 제거하는 대신, 불필요한 레지스터를 스택에 저장하고 복원하는 코드를 생성했다. 이로 인해 성능 저하가 발생했으며, 특히 `ADD2` 연산에서 이러한 문제가 두드러졌다. Rust 컴파일러의 최적화(Optimization)가 부족하여 어셈블리 구현에 비해 성능이 뒤쳐지는 결과를 낳았다.
WASM 환경에서의 성능 저하
WASM 환경에서 꼬리 재귀 인터프리터는 네이티브 Rust 구현보다 훨씬 낮은 성능을 보였다. 이는 WASM의 스택 머신(Stack Machine) 구조와 JIT 컴파일러의 최적화 한계 때문으로 분석된다. 특히, Firefox, Chrome, wasmtime에서 모두 네이티브 구현에 비해 현저히 낮은 성능을 기록하며, WASM 환경에서의 꼬리 재귀 최적화는 추가적인 연구가 필요함을 시사한다.
`become` 키워드의 제약 사항
커뮤니티에서는 `become` 키워드가 `Drop` 트레이트를 구현하는 값에 대한 소유권 이전을 지원하지 않는다는 점을 지적했다. 이는 Rust에서 Drop 가능한 값(Drop-able Values)을 많이 사용하는 관행과 충돌하여, 꼬리 재귀 최적화의 적용 범위를 제한한다. Drop 구현(Drop Implementation)으로 인해 `become` 사용에 제약이 발생하며, Rust의 꼬리 재귀 최적화에 대한 추가적인 개선이 필요하다는 의견이 제기되었다.