---
title: "Haskell 바이너리 크기 줄이기 — 링크 타임 최적화 두 가지 전략"
published: 2026-03-18T23:41:38.000Z
canonical: https://jeff.news/article/692
---
# Haskell 바이너리 크기 줄이기 — 링크 타임 최적화 두 가지 전략

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

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 코드에서 오동작함.

## 핵심 포인트

- -split-sections + --gc-sections로 데드 코드 제거: 113M → 83M (-27%)
- lld의 ICF(--icf=all)로 동일 섹션 병합: 83M → 64M (-23% 추가)
- ICF로 접힌 120K 섹션 중 절반이 pandoc 단일 ghc 호출에서 발생
- -fdistinct-constructor-tables와 ICF가 충돌해 디버깅용 info table이 제거될 수 있음
- 중복 섹션은 바이너리 크기뿐 아니라 컴파일 시간도 낭비 — GHC 캐싱 개선 여지 있음

## 인사이트

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