RAII, Rust/Linux의 환상
(kristoff.it)요약
Rust 개발자와 기존 Linux 개발자 사이의 분쟁을 지켜보고 쓰는 글입니다. 여러 개발자가 각자 다른 코딩 스타일을 가질 순 있으나, Linux 프로젝트는 이미 C++을 배제해서 그것의 코드 스타일과 구조(RAII)를 피한 전적이 있습니다.
Asahi Lina가 언급한 코드의 작동 방식은 그 프로그램을 종료할 때 너무 느리며, 성능 지향 소프트웨어를 만드는 데 가장 기초적인 방식인 일괄 작업과 대립합니다. 예를 들어, 메모리 영역을 사용해 일괄 작업을 하는 것은 여러 개의 수명을 하나로 조정할 수 있어 RAII가 필요 없습니다.
여기 제 주장을 뒷받침하는 자료를 제시합니다. 이 자료들 모두 일괄 작업이 왜 좋은지 알려주고 있습니다:
- Casey Muratori | Smart-Pointers, RAII, ZII? Becoming an N+2 programmer
- CppCon 2014: Mike Acton "Data-Oriented Design and C++"
- Modern Systems Programming: Rust and Zig - Aleksey Kladov
따라서 저는 Linux가 평생 RAII를 받아들이지 말아야 한다고 생각합니다.
제가 이 글을 가져온 이유는 이렇습니다. 한국의 러스트 개발자분들이 저 글을 보고 매우 화가 난 모습을 여러 번 봐서 여기 분들은 어떤 생각을 가졌을지 궁금했기 때문입니다. 여러분은 어떻게 생각하시나요.
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//…
주요 GC 언어들이 메모리는 GC 로 관리하지만 결국 파일핸들, 소켓과 같은 관리가 결정론적이어야 하는 리소스를 위해서
RAII 와 같은 구조나 (Java의 try-with-resources statement 나, C# 의 using statement, Python 의 with statement 등) 비슷한 구조 (Go 의 defer 등)
들을 추가로 도입 해서 결국 한 언어에 여러 리소스 관리 모드가 있게 되는데, 이런것보다 좀 더 낫지 않나 싶습니다.
제 견해지만 특정 개발자들의 엘리트 주의가 어느정도 이해는 갑니다. 소프트웨어 "공학" 적 관점에서, 특히 리눅스처럼 오늘날 오픈소스 진영에서 클로즈드 진영과도 두루두루 손을 잡고 오픈소스 철학의 진보에 도움이 된 "소프트웨어"는 찾아보기 어려운데, 검증되지 않은 프로그래머들이 Rust를 앞세워서 물밀듯 들어와 기존의 프로젝트 유지보수 중핵들의 통제를 벗어난 코드를 덕지덕지 붙혀 기술부채를 마구 늘려서 리눅스의 생명주기를 짧게 만들까봐 더욱 배타적이고 러다이트 같이 보이기마저 하는 보수적인 태도를 견지하는게 아닐까요?
오픈소스가 오픈소스로 오래 남아있기 위해 "오픈"적이지 않은 태도를 취하는 게 재미있습니다.
- Linux에서 C++를 배제함으로서 RAII를 피한 것은 맞지만, 그것이 Linux가 C++를 피한 것이 "RAII를 피하기 위해서" 라는 근거로는 부족해 보입니다. 제시하신 Linus의 서신에는 RAII라는 단어가 없습니다.
- 솔직히 말씀드리자면, Asahi Lina가... 로 시작하는 문단부터 제시된 링크는 모두 수십분 단위의 유튜브 영상이라, 보기가 어렵습니다. RAII가 필요 없는 사례 기반의 기술적인 예시를 들어주시면 논의가 더 활성화될 것 같습니다.
예를 들어, 메모리 영역을 사용해 일괄 작업을 하는 것은 여러 개의 수명을 하나로 조정할 수 있어 RAII가 필요 없습니다.
이것이 아레나를 의미하시는 거라면, Rust에도 당연히 아레나가 있으며 lifetime으로 아레나를 없앰으로서 "일괄 해제" 한 뒤에 아레나의 원소에 접근하는 것을 금지하는 것 또한 가능합니다. https://crates.io/keywords/arena 를 참고하시기 바랍니다.
저도 나름대로 Rust를 주력 언어로 사용하는 개발자인데 화는 나지 않았으나 좀 극단적인 예시("그 프로그램을 종료할 때 너무 느리며"라며 링크된 영상에서도 Rust 프로젝트에서의 사례와 직접적 관련은 없는, Visual Studio를 종료할 때 각 개별 컴퍼넌트의 소멸자가 호출되면서 너무 오래 걸린다는 사례를 들고 있습니다)를 들고 오는 게 아닌가 싶긴 합니다.
성능상 여러 컴퍼넌트의 Clean-up이 한 번에 처리되는 것이 필요한 경우에는 개별 컴퍼넌트에 대해 Drop을 구현하는 대신 해당 컴퍼넌트들의 수명을 들고 있는 타입에서 Clean-up을 한 번에 수행하도록 Drop을 구현하는 방식을 택할 수 있을 것 같습니다. 해당 컴퍼넌트를 그 타입의 API를 통해서만 생성할 수 있게 하는 안전장치를 두면 더 좋을 거고요.
물론 위 글의 저자분이 우려하는 바는 RAII를 사용하는 관행이 Linux 코드베이스에 들어올 경우 방대한 코드베이스에서의 복잡성 속에서 굉장히 암시적인 성능 우려가 있는 코드가 누적되면서 장기적으로 Visual Studio와 유사한 일이 벌어질 수 있다는 점일 것 같은데 이는 충분히 우려할만한 지점인 것 같습니다. 다만 다른 댓글에서 말씀하신 것처럼 RAII가 제공하는 안정성도 있으니 선택은 다소 트레이드오프라고 봅니다.
- RAII : Free를 각 객체가 없어질 때마다 실행
- 일괄실행? Free 해야할 것들을 모았다가 bulk로 실행?
리눅스에서 2가 1보다 더 빠르게 실행하도록하는 기능? api 같은게 있나요?
저는 당연히 1로 살았어서, 잘 이해가 안되서요.
정확하게는 모르지만 RAII를 안쓰겠다는건, 의도적인 메모리 누수를 이용해 (닫기)성능을 끌어올리겠다는거 같은데 이게 맞는지 방향인지 모르겠네요.
어짜피 수동으로 메모리 관리를 잘하는 개발자면 RAII도 잘 쓸것이고, RAII없이 개발 못하는 개발자는 메모리를 수동으로 관리도 못할것이니 RAII를 안쓸 이유가 없을것같습니다.
저도 RAII나 비슷한 형식의 리소스 관리를 자주 사용하고 권장하는 편입니다. RAII가 뭔지도 모르고 무지성적으로 사용해도 “일단은 안전한“ 코드가 나오기 때문이죠.
다만 제대로 알고 쓰지 않는다면 파일을 한번만 오픈해도 될것을 수십번 열고닫고 하는 식의 비효율적인 코드가 양산되기 십상입니다. 개발자가 성능에 꾸준히 관심을 가지고 있고 그런 문화가 개발팀에 전제가 된다면 RAII로도 충분한 수준의 성능을 낼 수 있다는 생각입니다.
양쪽 다 맞는 말을 하고 있습니다.
비유를 들자면, 온라인 게임 롤의 캐릭터 아지르는 스플릿 운영과 한타에서의 지역장악력, 그리고 궁 밸류가 압도적으로 좋은 고티어 챔프라는 인식이 있는데, 이건 고도로 숙련된 프로 경기에서나 통하는 얘기고, 일반인 레벨에선 라인전도 너무 약하고 체급도 약해서 그저 최하티어 챔피언일 뿐이죠.
아사히 리나처럼 상위 10% 이상의 프로그래밍 및 운영체제 지식이 있는 사람들의 입장에선 RAII 이외의 안이 당연히 좋겠지만, 나머지 90%가 다루는 부분에선 RAII나 Rust만한게 없다고 생각합니다.
다만 메모리 안정성/안전성을 보장해야 하는 큰 이유 중 하나에 보안 문제가 있기 때문에... tradeoff는 불가피하다고 봅니다.