35P by neo 2달전 | favorite | 댓글 6개
  • 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의 표준 라이브러리를 넘어서 사용자 정의 해시 테이블과 같은 데이터 구조를 구현하는 것이 중요한 역할을 함을 보여줌.
  • 병렬 처리의 효과를 강조하며, 단일 코어 최적화와 병렬화를 결합하여 놀라운 성능 향상을 달성함.
  • 이 기사는 성능에 민감한 애플리케이션을 개발하는 소프트웨어 엔지니어에게 유용한 인사이트를 제공함.
  • 이러한 최적화가 실제 프로덕션 환경에서 얼마나 유용할지는 사용 사례에 따라 다를 수 있음. 모든 애플리케이션에 이러한 수준의 최적화가 필요하지 않을 수 있음.

좋은 글 공유 감사합니다. 한때 시스템 최적화에 미쳐있던 때가 떠오르네요 ㅎㅎ
개발 경력이 쌓이면서 최고로 최적화된 코드는 유지보수가 힘들어서 조직 환경에서는 운용하기가 힘든 경험을 많이 하다보니 점차 최적화의 길에서 멀어지게 되었네요.(갑자기 개인 회고)

조직에 최적화된 코드!!

7번 스텝에서 구체적으로 어떤 작업이 이뤄졌는지가 궁금하네요. 성능향상이 굉장히 많이 일어난 구간인데 ㅋㅋ

각 스텝별로 구분지어 성능 향상 시간을 나타낸게 흥미 롭네요 ㅎ

wc 로도 1분이면.... 역시 최고는 코드를 작성하지 않는 거군요... 허허

Hacker News 의견
  • 첫 번째 사용자는 데이터 조작을 위한 코드 최적화 경험이 없었기 때문에, cat, wc 등을 사용해 기본 측정값을 얻는 첫 번째 섹션이 특히 흥미로웠다고 언급함. 이러한 방법은 "합리적인" 범위를 얻는 쉬운 방법이라고 생각함.
  • 두 번째 사용자는 Polars 라이브러리를 사용한 경우의 처리 시간이 33초임을 언급하며, 가장 빠른 수작업 최적화 솔루션에 근접하는 가장 간단한 솔루션에 대해 관심을 표현함.
  • 세 번째 사용자는 Go 언어의 성능 분석 보고서가 혼란스럽다고 언급하며, 특정 코드 라인의 실행 시간이 직관적이지 않을 경우, 데이터가 예측하기 어렵고 분기 예측기가 잘못 예측할 수 있다고 설명함.
  • 네 번째 사용자는 Go 언어로 1BRC(1 Billion Row Challenge)를 수행한 결과를 공유하며, Go 언어 특유의 최적화 기법들을 배웠다고 언급함. 예를 들어, unsafe.Pointer를 사용한 경계 검사 없는 메모리 읽기, 표준 라이브러리의 bytesbits 패키지 함수들이 어셈블리로 작성됨, 가비지 컬렉션을 끄는 설정, 스레드에 고루틴을 고정하는 방법 등이 있음.
  • 다섯 번째 사용자는 쉘 스크립트 개발자가 다른 언어 개발자들이 준비하는 동안 이미 특정한 10억 행의 데이터 처리를 완료했을 것이라고 주장함.
  • 여섯 번째 사용자는 데이터베이스가 애플리케이션 코드보다 빠르고, 덜 복잡하며, 데이터 업데이트에 더 강건하다고 주장하며, 데이터베이스에서 더 많은 작업을 수행해야 한다고 강조함.
  • 일곱 번째 사용자는 2010년에 PostgreSQL을 사용해 환경 캐나다의 기후 데이터 2억 7천만 행을 조회하는 웹 앱을 개발했으며, 이 소프트웨어가 수상한 경험을 공유함. 이 앱은 1분 이내에 보고서를 생성할 수 있도록 최적화되었음.
  • 여덟 번째 사용자는 Go 언어에서 병렬 코드가 여전히 Go의 관용적인 코드 스타일을 유지한다는 사실이 멋지다고 언급함.
  • 아홉 번째 사용자는 대규모 텍스트 파일을 CLI에서 다룰 때 유니코드 파싱을 생략하면 awk, grep 등이 한 차원 빠르다고 언급하며, awk 솔루션에 LC_ALL=C를 추가하면 1분 이내로 처리 시간을 단축할 수 있다고 주장함.
  • 마지막 사용자는 가장 빠른 Java 버전이 가장 빠른 Go 버전보다 빠르다는 것이 흥미롭다고 언급하며, 자바 가상 머신(JVM)의 성능이 상당히 좋다고 평가함.