Rust의 제로 코스트(Zero-Cost) 추상화, SIMD(Single Instruction, Multiple Data) 최적화를 막다?
Rust의 제로 코스트(Zero-Cost) 추상화가 SIMD(Single Instruction, Multiple Data) 최적화를 방해하여 성능 저하를 유발하는 사례를 소개함
풀 텍스트 검색(Full-text Search) 쿼리에서 이터레이터(Iterator) 사용으로 인해 필터링 성능이 50배 이상 감소하는 문제 발생
배치(Batch) 기반 이터레이터(Iterator)를 도입하여 SIMD(Single Instruction, Multiple Data) 최적화를 가능하게 하여 성능을 대폭 개선함
기계적 공감(Mechanical Sympathy)의 중요성을 강조하며, 추상화에만 의존하지 않고 성능 프로파일링(Performance Profiling)을 통해 병목 지점을 파악해야 함
제로 코스트(Zero-Cost) 추상화의 함정
Xavier Denis는 Rust의 제로 코스트(Zero-Cost) 추상화가 SIMD(Single Instruction, Multiple Data) 최적화를 방해하여 성능 저하를 유발하는 사례를 제시했다. 특히, 이터레이터(Iterator)의 next() 함수가 재귀적으로 호출되면서 컴파일러가 루프 언롤링(Loop Unrolling) 및 벡터화(Vectorization)를 수행하지 못하게 했다. 결과적으로, 100,000개의 값을 처리하는 데 6.5ms가 소요되어, 예상보다 50배나 느린 성능을 보였다.
성능 병목 지점: merge 이터레이터(Iterator)
문제의 핵심은 ContainsAny 필터 쿼리에서 여러 이터레이터(Iterator)를 병합하는 merge 이터레이터(Iterator)의 성능 저하였다. merge 이터레이터(Iterator)는 각 키 범위에 대한 이터레이터를 병합하여 정렬된 스트림을 생성하는데, next() 함수의 재귀적 호출로 인해 컴파일러가 최적화를 수행하지 못했다. 특히, 쿼리에 많은 필터 값이 포함될수록 merge 연산의 비용이 증가하여 전체 쿼리 성능을 저하시켰다.
배치(Batch) 기반 이터레이터(Iterator)를 통한 해결
해결책은 배치(Batch) 기반 이터레이터(Iterator)를 도입하여 SIMD(Single Instruction, Multiple Data) 최적화를 가능하게 하는 것이었다. next_batch() 함수를 통해 여러 값을 묶어 처리함으로써, 컴파일러가 루프를 최적화하고 SIMD(Single Instruction, Multiple Data) 명령어를 사용할 수 있게 되었다. 그 결과, 100,000개 값 처리 시간이 110μs로 단축되어, 기존 대비 60배 이상 향상된 성능을 보였다.
기계적 공감(Mechanical Sympathy)의 중요성
저자는 제로 코스트(Zero-Cost) 추상화가 모든 문제를 해결하는 것은 아니며, 기계적 공감(Mechanical Sympathy)을 통해 성능 병목 지점을 파악하고 해결해야 한다고 강조했다. 성능 프로파일링(Performance Profiling)을 통해 실제 병목 지점을 찾아내고, 적절한 아키텍처(Architecture) 및 구현 방식을 선택하는 것이 중요하다는 점을 시사한다. 또한, 데이터베이스(Database) 쿼리 최적화에 배치(Batch) 기반 이터레이터(Iterator)와 같은 전통적인 기술이 여전히 유효함을 보여준다.