10만 개 파일, 렉싱(Lexing)보다 I/O가 43배 빨라지는 마법
ARM64 어셈블리 렉서(Lexer)를 개발하여 기존 렉서 대비 2배 빠른 성능을 달성했지만, I/O 병목 현상으로 인해 전체 속도 향상은 미미했음.
10만 개 이상의 파일을 처리하는 과정에서 시스템 콜(System Call) 오버헤드가 주요 병목 지점임을 확인, 파일 개수를 줄이는 방식으로 해결 시도.
개별 파일 대신 tar.gz 아카이브(Archive)를 사용한 결과, I/O 속도가 43배 향상되었으며, 패키지 매니저의 아카이브 방식 채택 이유를 설명함.
압축 해제(Decompression) 및 압축(Compression) 방식 개선, SQLite 활용 등 추가적인 I/O 최적화 방안에 대한 커뮤니티 논의가 이어짐.
렉서(Lexer) 성능 개선과 I/O 병목 현상
저자는 ARM64 어셈블리 렉서(Lexer)를 개발하여 기존 렉서 대비 2배 이상 빠른 성능을 달성했지만, 실제 10만 개 이상의 파일을 처리하는 과정에서 I/O 병목 현상에 직면했다. 특히, 파일을 읽는 데 소요되는 시간이 렉싱(Lexing) 시간보다 훨씬 길어 전체적인 성능 향상을 저해했다. 이러한 경험을 통해 렉서 자체의 성능 개선보다 I/O 최적화가 더 중요함을 깨달았다고 한다.
시스템 콜(System Call) 오버헤드 분석
분석 결과, I/O 병목 현상의 주요 원인은 시스템 콜(System Call) 오버헤드였다. 10만 개 이상의 파일을 처리하기 위해 30만 번 이상의 open(), read(), close() 시스템 콜이 발생했고, 각 시스템 콜은 컨텍스트 스위칭(Context Switching) 및 권한 검사(Permission Checks)를 포함하여 1~5 마이크로초의 오버헤드를 발생시켰다. 이러한 오버헤드가 누적되어 I/O 성능을 저하시키는 주요 원인으로 작용했다.
tar.gz 아카이브(Archive)를 활용한 I/O 최적화
저자는 I/O 병목 현상을 해결하기 위해 개별 파일 대신 tar.gz 아카이브(Archive)를 사용하는 방법을 제안했다. 10만 개 이상의 개별 파일을 1,351개의 tar.gz 아카이브로 묶어, 시스템 콜 횟수를 30만 번에서 4천 번으로 줄였다. 그 결과, I/O 속도가 43배 향상되었으며, 패키지 매니저(Package Manager)가 아카이브 방식을 채택하는 이유를 설명할 수 있었다.
압축 해제(Decompression) 및 압축(Compression) 방식 개선
커뮤니티에서는 gzip 대신 zstd를 사용하여 압축 해제 속도를 4~5배 향상시키거나, 압축을 사용하지 않고 tar 아카이브를 캐싱하는 방안을 제안했다. 또한, 병렬 처리를 위해 여러 코어를 사용하여 여러 아카이브를 동시에 압축 해제하는 방법도 제시되었다. 이러한 방법들을 통해 I/O 성능을 더욱 향상시킬 수 있을 것으로 기대된다.
SQLite를 활용한 I/O 최적화
tsanderdev는 SQLite를 사용하여 파일 시스템 콜(File System Call) 오버헤드를 제거하는 방법을 제안했다. SQLite는 파일 내용을 SQLite 데이터베이스에 저장하여 시스템 콜 오버헤드를 줄이고, 무작위 접근(Random Access)을 가능하게 한다. 이는 tar.gz가 제공하지 못하는 장점이다. Apple이 SQLite를 광범위하게 사용하는 이유도 이와 같은 I/O 성능 개선에 기인한다.
링커(Linker) 전략 및 래퍼(Wrapper) 프로세스 활용
MaskRay는 링커(Linker)가 사용하는 I/O 최적화 전략을 소개했다. mold 링커는 래퍼(Wrapper) 프로세스를 사용하여 자원 해제를 지연시키고, lld는 _exit를 호출하여 C 런타임(C Runtime)의 정리 루틴을 건너뛴다. 또한, matthieum은 컴파일러와 같이 일회성 배치 프로세스(Batch Process)에서 자원 해제를 생략하여 성능을 향상시키는 방법을 제안했다. 이러한 방법들은 I/O 성능을 개선하는 데 도움이 될 수 있다.