루프 vs 반복자의 성능
루프 vs 반복자의 성능 (Performance in Loops vs. Iterators)
루프를 쓸지 반복자를 쓸지 결정하려면 어느 구현이 더 빠른지 알아야 해요. 명시적 for 루프를 쓰는 search 함수 버전과 반복자를 쓰는 버전 말이에요.
실제로 벤치마크를 돌려 봤어요. 셜록 홈즈의 The Adventures of Sherlock Holmes 전체 내용을 String에 로드하고 그 내용에서 the라는 단어를 찾는 방식으로요. for 루프를 쓰는 search 버전과 반복자를 쓰는 버전의 벤치마크 결과가 다음과 같아요:
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
두 구현의 성능은 비슷해요! 여기서 벤치마크 코드를 자세히 설명하지는 않을게요. 두 버전이 동등하다는 걸 증명하는 게 목적이 아니라, 성능 측면에서 두 구현을 어떻게 비교하는지 대략적인 감을 얻는 게 목적이거든요.
더 포괄적인 벤치마크를 원한다면 다양한 크기의 텍스트를 contents로, 다양한 단어와 다양한 길이의 단어를 query로 써 보는 등 여러 변주를 시도해 보세요. 핵심은 이거예요. 반복자는 높은 수준의 추상화이지만, 컴파일되면 직접 낮은 수준의 코드를 작성한 것과 거의 같은 코드로 내려갑니다. 반복자는 러스트의 제로 비용 추상화(zero-cost abstraction) 중 하나예요. 즉 추상화를 쓰는 것이 추가적인 런타임 오버헤드를 전혀 부과하지 않는다는 뜻이죠. C++의 원래 설계자이자 구현자인 비야네 스트로스트룹(Bjarne Stroustrup)이 2012년 ETAPS 기조 강연 "Foundations of C++"에서 zero-overhead를 정의한 방식과 비슷해요:
일반적으로 C++ 구현은 zero-overhead 원칙을 따릅니다. 쓰지 않는 것에 대해서는 비용을 지불하지 않는다. 더 나아가, 쓰는 것에 대해서는 손으로 직접 코드를 짠 것보다 더 잘할 수 없다.
많은 경우 반복자를 사용하는 러스트 코드는 손으로 직접 작성했을 어셈블리와 똑같이 컴파일돼요. 루프 언롤링(loop unrolling)과 배열 접근의 경계 검사 제거 같은 최적화가 적용되어 결과 코드가 극도로 효율적으로 만들어져요. 이제 이 사실을 알았으니 반복자와 클로저를 두려움 없이 쓸 수 있어요! 코드가 더 높은 수준처럼 보이지만, 그렇다고 런타임 성능 비용을 부과하지는 않으니까요.
출처: The Rust Book
요약 (Summary)
클로저와 반복자는 함수형 프로그래밍 언어의 아이디어에서 영감을 받은 러스트 기능이에요. 이들은 러스트가 낮은 수준의 성능으로 높은 수준의 아이디어를 명확하게 표현할 수 있는 능력에 기여해요. 클로저와 반복자의 구현은 런타임 성능에 영향을 주지 않도록 되어 있어요. 이것은 러스트가 제로 비용 추상화를 제공하려는 목표의 일부예요.
이제 I/O 프로젝트의 표현력을 개선했으니, 프로젝트를 세상과 공유하는 데 도움이 되는 cargo의 더 많은 기능을 살펴보죠.