본문으로 건너뛰기
피드

Haskell 바이너리 크기 줄이기 — 링크 타임 최적화 두 가지 전략

backend 약 4분
vote
0
댓글
북마크

GHC 9.2.5 환경에서 pandoc 바이너리를 대상으로 -split-sections/--gc-sections와 Identical Code Folding(ICF) 두 가지 링크 타임 기법을 적용해 113M → 64M(-43%)으로 줄인 실험 공유.

  • 1

    -split-sections + --gc-sections로 데드 코드 제거: 113M → 83M (-27%)

  • 2

    lld의 ICF(--icf=all)로 동일 섹션 병합: 83M → 64M (-23% 추가)

  • 3

    ICF로 접힌 120K 섹션 중 절반이 pandoc 단일 ghc 호출에서 발생

  • 4

    -fdistinct-constructor-tables와 ICF가 충돌해 디버깅용 info table이 제거될 수 있음

  • 5

    중복 섹션은 바이너리 크기뿐 아니라 컴파일 시간도 낭비 — GHC 캐싱 개선 여지 있음

Haskell 프로젝트는 의존성이 쌓이다 보면 바이너리가 100MB를 훌쩍 넘기기 일쑤임. GHC 9.2.5 환경에서 pandoc의 test-pandoc 바이너리로 실험한 두 가지 링크 타임 최적화 전략 공유.

전략 1: -split-sections + --gc-sections (데드 코드 제거)

GHC에 -split-sections 옵션을 주면 코드를 개별 섹션 단위로 쪼개서 emit함. 그러면 링커가 실제로 참조되지 않는 섹션(데드 코드)을 쉽게 찾아 제거할 수 있음.

cabal.project 설정:

package *
  ghc-options: -split-sections
  gcc-options: -fdata-sections -ffunction-sections
package pandoc
  ld-options: -fuse-ld=lld -Wl,--gc-sections,--build-id
  • lld를 쓰는 이유는 빠르고, 아래 전략 2도 지원하기 때문
  • 결과: 113M → 83M (-27%) 감소

전략 2: Identical Code Folding (ICF)

gold와 lld 모두 ICF를 지원하는데, 링크 타임에 기능적으로 동일한 섹션을 찾아서 하나로 합쳐버리는 방식임. lld의 구현이 더 효과적이라고 함.

전략 1 설정에 추가:

ld-options: -Wl,--icf=all,--ignore-data-address-equality,--ignore-function-address-equality,--print-icf-sections
  • 결과: 83M → 64M (추가 -23%)
  • 최종 총 감소: 113M → 64M (-43%)

왜 Haskell에서 중복 코드가 많나?

같은 함수가 여러 모듈에 인라인·특수화되면서 결과적으로 동일한 바이트코드가 반복 생성되는 경우가 많음. 실험에서 ICF로 접힌 120K개 섹션 중 절반이 pandoc 자체에서 나왔음 — 단 하나의 ghc 호출에서 비롯된 것.

주의할 점 및 추가 생각

  • -fdistinct-constructor-tables와 충돌: 프로파일링·디버깅용 info table 중복본이 ICF에 의해 제거돼버림. 디버깅 목적으로 쓸 때는 주의 필요.
  • 컴파일 시간 낭비 문제: 이 중복 섹션들은 바이너리 크기만 낭비하는 게 아니라 컴파일 시간(코드 생성, simplifier 등)도 낭비하는 것. GHC가 동일하게 emit될 컴파일 유닛을 캐싱할 수 있다면 빌드 속도도 개선될 여지가 있음.
  • bloaty 툴로 바이너리 구성을 분석하려 했지만 Haskell 코드에서 오동작함.

Haskell 특성상 인라인·특수화로 동일 코드가 여러 모듈에 반복 생성되는 구조적 원인이 있어 ICF 효과가 특히 큼. 단순 옵션 추가만으로 43% 감소는 인상적이며, GHC 컴파일러 레벨의 중복 캐싱 최적화로 이어질 수 있는 흥미로운 방향을 제시함.

댓글

댓글

댓글을 불러오는 중...

backend

잘못된 추상화보다 중복이 낫다는 샌디 메츠의 고전 조언

샌디 메츠는 중복을 없애려다 잘못된 추상화를 만들면 코드가 조건문과 파라미터로 부풀어 더 위험해진다고 말한다. 이미 틀어진 추상화는 억지로 보존하지 말고, 다시 호출부에 인라인해서 중복을 되살린 뒤 현재 요구사항에 맞는 새 구조를 찾는 편이 빠르다는 주장이다.

backend

리눅스 커널, 6년·360개 넘는 패치 끝에 strncpy 제거

리눅스 커널이 오랫동안 버그의 원인이던 strncpy API 사용을 Linux 7.2에서 제거했어. NUL 종료 동작이 직관적이지 않고 불필요한 zero-fill로 성능 문제도 있던 API를 6년 동안 약 362개 커밋으로 걷어낸 작업임.

backend

덕디비는 왜 빠를까: 서버 없는 분석 엔진의 내부 구조 뜯어보기

DuckDB가 단일 바이너리, 인프로세스 실행, 컬럼형 저장, 최적화 패스, Parquet 푸시다운으로 빠른 분석 쿼리를 처리하는 방식을 깊게 설명한 글이다. 6GB Parquet 파일을 노트북에서 바로 SQL로 읽는 경험 뒤에 어떤 설계가 깔려 있는지 따라간다.

backend

피지독, 포스트그레스를 수평 확장시키겠다고 550만 달러 투자 유치

피지독은 포스트그레스 앞단에 프록시를 두고 샤딩과 라우팅을 처리해 수평 확장을 가능하게 하겠다는 오픈소스 프로젝트다. 이미 프로덕션에서 초당 200만 건이 넘는 쿼리를 처리하고, 확인된 규모만 20테라바이트 이상을 샤딩했다고 밝히며 550만 달러 투자를 공개했다.

backend

펜타시스템, EDB 포스트그레SQL로 국내 엔터프라이즈 DB 전환 시장 공략

펜타시스템테크놀러지가 EDB와 파트너 계약을 맺고 국내에 EDB 포스트그레SQL 기반 데이터 플랫폼을 공급한다. 기존 상용 DBMS 정책 변화로 비용 부담이 커진 기업들을 겨냥해, 오픈소스 기반 엔터프라이즈 데이터 플랫폼 전환 수요를 잡겠다는 전략이다. 금융, 공공, 제조, 유통, 클라우드, AI 데이터 분석 환경까지 적용 범위를 넓히려는 움직임이다.