[펌] Rust는 과연 C++ 킬러인가?

나의 재물운? 연애운은?

AI가 봐주는 내 사주팔자 - 운세박사

https://aifortunedoctor.com/

[펌] Rust는 과연 C++ 킬러인가?

케빈킴 0 3,274 2022.10.15 10:58

Rust는 과연 C++ 킬러인가?

작성자: 배현직



30년 C/C++ 써온 저의 Rust 수박 겉핥기 후기를 끄적여봅니다.



결론부터 말씀드리자면, Rust가 C++ 킬러까지는 가지는 못할 것입니다. 하지만 여러분이 성능이 예민한 프로그램을 새로 개발해야 한다면, Rust를 시도해보시는 것도 좋습니다. 충분히 그럴만한 가치가 있습니다. 그렇지만 은탄환까지는 기대하지 마세요. 그래도 안 쓰는 것보다는 낫습니다.



자 이제 썰 들어갑니다. 두서없이 마구 적을테니 벨트 잘 붙잡으세요!



Rust의 장점


C/C++이 다른 언어에 비해 가지고 있는 큰 단점은, C/C++이 가지고 있는 include 구문입니다. C/C++은 헤더파일 include라는 것 때문에, 대체적으로 O(n^2) 시간복잡도의 소스파일 파싱을 하게 됩니다. 이를 줄이기 위해 Precompiled Header File 같은 것을 이용하기도 합니다만, 그렇다 하더라도 여러분이 짠 백만줄짜리 소스코드까지 그 혜택을 보지는 못합니다.



Rust는 생긴걸 보아하니, 그렇지는 않습니다. 즉 O(n) 시간복잡도의 소스파일 파싱을 합니다. 일단 여기서부터 C,C++을 압도하는 컴파일 속도를 보여주게 됩니다. 그러나 이게 별로 대단할 것도 아닌 것이, C/C++이 O(n^2) 시간복잡도의 소스파일 파싱을 한다는 결함을 가진 언어인 것이지, C/C++을 제외한 여타 모든 언어는 이렇게 바보같은 컴파일을 하지 않습니다.



C/C++이 다른 언어에 비해 가지고 있는 또 다른 단점은, 지나친 전방선언(forward declaration) 의존입니다. 여타 언어들은 대부분 전방선언이 없더라도 정상적으로 빌드가 됩니다. 전방선언은 언어 사용자(프로그래머)에게 주의산만함을 조금이라도 주게 됩니다. 그까짓 주의산만함이 그렇게 문제가 되냐고요? 수만줄짜리 코딩으로만 넘어가면 여차하면 튀어나오는게 헤더파일 정리 노가다로 이어지는 원흉이기도 합니다. 이유도 목적도 효과도 아무 도움도 안되는 이 시간낭비 해프닝은, C/C++ 언어에서만 경험하는 고질이기도 합니다. 다행히, Rust는 사용자가 코딩하는 것 이외의 런타임 코드가 뒤섞이지 않는 언어(Pascal 등)이면서도 전방선언이 불필요한 언어입니다.



일단 이 두가지만 놓고 봐도 Rust는 C/C++보다는 큰 장점을 줍니다. 작은 프로젝트를 할 때는 모르겠지만, 백만줄 레벨의 대규모 개발 프로젝트에서 사소한 리팩토링 한번 해도, 이 두가지의 차이가 얼마나 개발 효율성에 영향을 주는지를 아실 수 있습니다. 리팩토링 한번하고 그걸로 끝도 아닙니다. 백만줄 레벨 코딩에서 누가 이거 리팩토링 후 커밋 때려보세요. 코드리뷰 담당자들 한명한명 찾아다니면서 미안하다고 커피라도 사들고 다녀야 할겁니다.



C언어의 매크로는, plain text substitution 즉 단순 문자열 대체를 합니다. 그러나 C언어의 매크로는 지나칠정도로 텍스트 대체를 지원하다보니, 문자를 기호로 대체하는 엽기적인 것까지 지원합니다. 개인적으로 이는 불필요한 오버스펙이고, 오히려 개발자들을 지속적으로 괴롭혀온 결함(glitch)이라고 불러도 될 정도로 부끄러운 스펙이라고 생각합니다.



개인적으로 Rust의 가장 큰 장점은, Rust 특유의 매크로(macro)인 것 같습니다. Rust에서는 declarative macro라는 것이 있는데, 이게 꽤 물건입니다. BNF 문법을 직접 사용해서 매크로 안에 들어가는 파라메터를 여러분이 자유롭게 정의할 수 있습니다. 그리고 매크로 본문 안에서 이 파라메터 내용을 근거로 여러분만의 코드 생성 규칙을 정의하실 수 있습니다.



그리고 Rust의 procedural macro 등에서는 매크로의 끝판왕을 보여줍니다. C의 쓸데없는 지나친 치환 기능을 버리고, 대신 여러분은 매크로 안에 들어간 파라메터 내용에 따라, 여러분의 컴파일 과정 중에 여러분의 코드를 실행해서, 여러분이 원하는 코드를 컴파일 타임에 자유롭게 생성할 수 있습니다. 이것이 있기 전까지는 여러분은 custom compile 프로그램을 개발하거나 도입하는 등의 수고를 해야 합니다. 그런데 Rust에서는 이것을 자체적으로 지원합니다. 이것을 잘 활용하면 RPC 등을 쉽게 개발할 수 있고요, 특히 C/C++의 이중삼중 비비꼬인 template metaprogramming이라는, 이름은 화려하나 거대한 똥덩어리를, 시원하게 물내림 할 수 있습니다. 템플릿 메타프로그래밍이 얼마나 쓰레기인지는, 많은 팀원들로 구성된 대규모 개발 프로젝트를 해보신 분이라면, 다들 공감하실테니 길게 설명하지는 않겠습니다.



C에서는 variadic arguments를, Modern C++에서는 template variadic arguments라는 쓰레기 기능이 제공됩니다. Rust에서는 이를 상기 매크로를 이용해서 단순화할 수 있습니다. 생각해보세요. 컴파일 타임에서 파라메터 갯수가 caller 측면에서 이미 정의됐는데, 그걸 런타임에서 루프를 돌면서 처리한다는게 얼마나 쓰레기같은 짓입니까? 성능도 떨어지고 가독성도 떨어지는 쓰레기 설계입니다. Rust는 이를 매크로로 속시원히 물내림 해버렸습니다. 또한 C++ template variable arguments처럼 컴파일 타임에서 처리합니다. 그러면서도 가독성은 Modern C++ 케이스보다 비교불가 수준으로 높습니다.



Rust는 클래스 상속 대신에 traits라는 개념을 제공합니다. 클래스 상속에 익숙하신 분들은 여기서 당황할 수 있겠지만, 한 클래스 안에 멤버함수가 길게 늘어지는 Java 등 다른 언어를 쓰셨던 분들은 여기서 뭔가 상쾌함을 느끼실 수 있습니다. Rust가 클래스 상속 개념 대신에 이렇게 코딩할 수 있게 해놓은 것은, 한두번 코딩해보지 않은 사람이 설계한 언어라는 흔적이겠죠.



Rust의 enum은 신박합니다. Rust의 ownership을 이용한 사기캐 수준입니다.



그런데 왜 여기까지 잘 Rust의 장점을 설명해놓고, 갑자기 왜 “C++ 킬러까지는 아니다”라고 말하는지 언급해 보겠습니다.


Rust의 단점


Rust를 설계한 분이 어떤 경력을 갖고 있으신지는 모르겠지만, C로 대규모 프로젝트를 만든 것 보다는 C++로 대규모 프로젝트를 해본 경험으로 언어 설계를 하신 것 같습니다. Rust는 전반적으로 Modern C++를 저격하여 설계된 언어 같고요, C로 개발하거나 Modern C++을 적극 활용하지 않는 코딩까지 속속들이 커버하지는 않은 것 같습니다.



C++은 C 위에 1980년대부터 수십년간 향유하던 객체지향 프로그래밍 철학이 씌워진 언어입니다. 그리고 Modern C++은 전통 C++ 위에 템플릿, Standard Template Library 등이 올라가면서 이른바 “나룻배 위에 항공모함이 얹어지는” 기행의 결과라고 생각합니다. 이를 크게 단순화시켜버리자는 것이 Rust의 제작의도가 아니었나 의심됩니다.



C++은 다른 언어와 달리, 동적크기 객체 즉 heap allocated memory block에 대한 자동화된 관리를 위해 reference counting을 합니다. Java 등 다른 언어는 Garbage Collection으로 관리되고 있죠. reference counting은 Garbage Collection보다는 구조가 단순합니다. 하지만 성능상으로는 사용 여하에 따라 오히려 더 나쁩니다. assignment 구문이 실행될때마다 reference count가 변화를 하는 연산을 쓸데없이 해야 합니다. 멀티스레드 환경에서는 atomic operation으로 작동하기 때문에, contention이 발생할 경우 엄청난 (수십배) 속도저하가 일어나게 됩니다. 안 발생해도 일단 두배 먹고 들어갑니다. C++을 성능 예민한 프로그램 개발에 사용할 때 shared_ptr을 그래서 웬만하면 안 쓰는겁니다. (혹은 개발팀원 모두가 주의사항을 따르며 씁니다.)



암튼간에 C++ (정확히는 Standard Library)는 reference counting이라는 특이한 자동 메모리 관리를 합니다. 그러나 이것은 앞서 말씀드린 성능저하 케이스 뿐만 아니라, cyclic reference로 발생하는 메모리 누수 문제도 가지게 됩니다. 그리고 더 큰 문제는, 지저분한 코드죠. unique_ptr 쓰다가 shared_ptr로 바꾸는 리팩토링도 했다가 여기저기서 weak_ptr 썼다가 raw ptr 썼다가… 특히 멀티스레드 프로그래밍에서는 이거 참 골치아픕니다. Garbage Collection보다 10배는 개발 집중력을 떨어뜨립니다.



게임개발에서 렌더링 파이프라인, 물리 시뮬레이션이나, 동영상 인코딩 등 실시간 처리가 중요한 상황에서는, Garbage Collection도 잦은 heap allocation도 안씁니다. 즉 shared_ptr 조차도 안쓰거나, 주의하며 씁니다.



자, 그럼 이런 거지같은 상황을 Rust는 어떻게 해결할까요? 안타깝게도 완전한 해결을 안 해줍니다. 네. Rust도 reference counting 방식입니다.

Comments

나의 재물운? 연애운은?

AI가 봐주는 내 사주팔자 - 운세박사

https://aifortunedoctor.com/

Category
실시간 인기글
Magazine
훈남/훈녀
 
 
 
상점
Facebook Twitter GooglePlus KakaoStory NaverBand