foriequal0 2024-11-29 | parent | ★ favorite | on: RAII, Rust/Linux의 환상(kristoff.it)

Free가 얼마나 시간을 소모하는지 궁금해서 실제 워크로드와는 많이 다르겠지만, 간단하게 코드를 짜서 테스트해봤습니다. (Rust 릴리즈 빌드로, std::alloc::alloc이랑 std::fs::File을 사용했습니다.)
다양한 크기의 메모리를 10,000,000개, 약 2.5GB 분량으로 할당해두고, 해제하는 시간만을 측정했더니 1.87초가 걸렸습니다. 개당 187ns 꼴입니다.
반면 파일의 경우, 핸들 10,000개 정도만 열어두고, 닫는 데 걸리는 시간만 측정했더니 약 9초였습니다. 파일 하나당 900us가 걸린 셈입니다.
(이 윈도우 PC가 백신 때문인지 파일 작업이 유독 느리긴 합니다. 다른 윈도우 노트북에서는 각각 400ns/200us, 다른 리눅스 PC에서는 50ns/600ns가 걸리더군요.)

RAII의 대안으로 벌크 처리라던가, 종료 시 OS를 믿고 리소스를 누수시키는 방법이 많이 언급되는데요, 메모리라면 쉽게 가능하겠습니다.
그런데 파일이나 소켓 같은 리소스는 벌크 회수 API를 본 적이 없고, 리소스를 누수시키면 유저 코드에서의 시간은 줄어들지 몰라도, 줄어든 만큼 고스란히 커널이 프로세스를 종료하는 데 걸리는 시간이 늘어나서 성능적 이득이 별로 없습니다.

메모리 RAII는 상대적으로 그렇게 느린 것도 아니고, arena 사용을 불가능하게 하는 기술도 아니며, 필요하면 의도적인 누수를 못 하게 하는 것도 아니라서 RAII 를 기피할 이유가 되긴 어려운 것 같습니다.
그리고 더 느린 파일 RAII의 경우, 벌크로 처리할 방법도 없고, 비용을 피할 방법도 없는 상황에서, RAII의 대안은 얼마나 더 나은지 궁금합니다.


약간 논외지만, RAII 와 lifetime 에 관한 반론들이 malloc/free 로 대표되는 메모리 자원에만 한정지어서 논의된다는 인상을 받습니다.
RAII 와 lifetime 은 메모리 할당 뿐 아니라, 파일, 소켓, 락같은 OS 자원 뿐 아니라, 객체 풀, 커넥션 풀 등과 같이
획득과 반환을 하고, 획득중 배타적 접근 제어가 필요한 대부분의 리소스 모델링에 두루두루 유용합니다.

이런 리소스들도 malloc/free 과 같은 구조를 공유 하기 때문에 누수, use after free, double free 와 같은 구조의 문제를 공유하고,
같은 구조를 공유하는 덕에 RAII 와 lifetime 가 메모리 뿐 아니라 이런 리소스들의 문제까지도 한꺼번에 해결한다는 점이 더 조명 될 필요가 있다 생각합니다.
예를 들어 Rust 에서는 파일 핸들에 대해서도 use after close, double close 를 컴파일타임에 방지합니다:
https://play.rust-lang.org/?version=stable&mode=debug&edition=…

주요 GC 언어들이 메모리는 GC 로 관리하지만 결국 파일핸들, 소켓과 같은 관리가 결정론적이어야 하는 리소스를 위해서
RAII 와 같은 구조나 (Java의 try-with-resources statement 나, C# 의 using statement, Python 의 with statement 등) 비슷한 구조 (Go 의 defer 등)
들을 추가로 도입 해서 결국 한 언어에 여러 리소스 관리 모드가 있게 되는데, 이런것보다 좀 더 낫지 않나 싶습니다.