- 1BRC : 10억행이 있는 텍스트 파일에서 온도 측정값을 읽어 관측소별 최소/평균/최대 온도를 계산하는 코드를 작성하는 챌린지
- 2024년 1월 1일부터 1월 31일까지 진행했으며, 최신 Java를 최대한 활용하는 것이 목표였음
- 이에 대해 사람들이 관심을 가지고 다양한 언어(Rust,Go,C++,SQL)로 도전하기 시작
- Go로 작성한 9가지 솔루션에 대해서 상세 소개 (느린것부터 빠른 것순으로)
기본 측정값
cat명령어를 사용하여 10억행 텍스트데이터(13GB) 데이터를 읽는 데 걸리는 시간은 1.052초임.- 실제로 파일을 처리하는
wc명령어는 거의 1분이 걸림(55.710초). - AWK 솔루션을 사용하여 문제를 해결하는 데 걸리는 시간은 7분 35초임.
솔루션 1: 간단하고 관용적인 Go
- Go 표준 라이브러리를 사용한 첫 번째 솔루션은 1분 45초가 걸림.
bufio.Scanner로 줄을 읽고,strings.Cut으로 ';'를 기준으로 분리함.strconv.ParseFloat로 온도를 파싱하고, Go 맵을 사용하여 결과를 누적함.
솔루션 2: 포인터 값이 있는 맵
- 맵에서 두 번의 해싱을 피하기 위해
map[string]*stats를 사용함. - 포인터 값을 사용하여 시간을 1분 45초에서 1분 31초로 단축함.
솔루션 3: strconv.ParseFloat 피하기
strconv.ParseFloat대신 사용자 정의 코드를 사용하여 온도를 파싱함.- 시간을 1분 31초에서 55.8초로 단축함.
솔루션 4: 고정 소수점 정수 사용
- 온도를 정수로 표현하여 부동 소수점 연산을 피함.
- 시간을 55.8초에서 51.0초로 단축함.
솔루션 5: bytes.Cut 피하기
- ';'를 찾기 위해 전체 스테이션 이름을 스캔하는 대신 끝에서부터 파싱함.
- 시간을 51.0초에서 46.0초로 단축함.
솔루션 6: bufio.Scanner 피하기
bufio.Scanner를 제거하고 파일을 큰 청크로 읽음.- 시간을 46.0초에서 41.3초로 단축함.
솔루션 7: 사용자 정의 해시 테이블
- Go의 맵 대신 사용자 정의 해시 테이블을 구현함.
- 시간을 41.3초에서 25.8초로 단축함.
솔루션 8: 청크 병렬 처리
- 간단하고 관용적인 코드를 병렬화하여 시간을 1분 45초에서 24.3초로 단축함.
솔루션 9: 모든 최적화 및 병렬 처리
- 모든 최적화를 병렬 처리와 결합하여 시간을 24.3초에서 3.99초로 단축함.
결과 테이블
- 모든 Go 솔루션과 가장 빠른 Go 및 Java 솔루션을 비교한 표 제공.
- Go 버전 중 가장 빠른 것은 2.90초, Java 버전은 0.953초로 처리함.
- 1초도 안걸리는 Java버전은 Thomas Wuerthinger(GraalVM 제작자)가 한 것으로, 이 분야 전문가이기 때문에 가능한듯
최종 코멘트
- 일상적인 프로그래밍 작업에서는 간단하고 관용적인 코드가 좋은 출발점임.
- 데이터 처리 파이프라인을 구축하는 경우, 코드를 4배 또는 26배 빠르게 만들면 사용자 만족도를 높이고 컴퓨팅 비용을 절약할 수 있음.
- 런타임이나 인터프리터를 구축하는 경우, 성능 향상이 중요함.
GN⁺의 의견
- 이 기사는 Go 언어를 사용하여 대규모 데이터 처리를 최적화하는 다양한 방법을 탐구함으로써, 성능 최적화에 대한 흥미로운 사례 연구를 제공함.
- 최적화 과정에서 Go의 표준 라이브러리를 넘어서 사용자 정의 해시 테이블과 같은 데이터 구조를 구현하는 것이 중요한 역할을 함을 보여줌.
- 병렬 처리의 효과를 강조하며, 단일 코어 최적화와 병렬화를 결합하여 놀라운 성능 향상을 달성함.
- 이 기사는 성능에 민감한 애플리케이션을 개발하는 소프트웨어 엔지니어에게 유용한 인사이트를 제공함.
- 이러한 최적화가 실제 프로덕션 환경에서 얼마나 유용할지는 사용 사례에 따라 다를 수 있음. 모든 애플리케이션에 이러한 수준의 최적화가 필요하지 않을 수 있음.