Bun Install의 비하인드 스토리

5 days ago 8

  • Bun의 패키지 설치는 기존 패키지 매니저들에 비해 매우 빠른 속도로 동작함
  • 빠른 설치의 핵심은 시스템 프로그래밍 관점의 접근시스템 콜 최소화에 있음
  • Zig 언어 기반의 네이티브 코딩, 바이너리 캐시 사용, OS별 최적화 등의 세밀한 전략 적용을 통한 성능 향상 제공
  • tarball 압축 해제 및 파일 복사 과정에서도 하드웨어 특성을 활용한 고성능 방법을 도입함
  • 의존성 그래프와 lockfile 등 데이터 구조 최적화를 통해 CPU 캐시 효율과 메모리 접근성을 높임

Bun Install이 빠른 이유

  • Bun의 bun install은 평균적으로 npm보다 7배, pnpm보다 4배, yarn보다 17배 더 빠른 패키지 설치 성능을 제공함
  • 이는 단순 벤치마크가 아닌, 패키지 설치 문제를 자바스크립트가 아닌 시스템 프로그래밍 관점으로 접근했기 때문임
  • 시스템 콜 최소화, 매니페스트 바이너리 캐싱, tarball 추출 최적화, OS 네이티브 파일 복사 등 여러 계층에서 성능 최적화를 적극적으로 적용함

Node.js와 패키지 매니저 아키텍처의 한계

  • 2009년 Node.js 출시 이후, 이벤트 루프와 쓰레드풀 기반의 비동기 IO 모델이 패키지 매니저에도 전파됨
  • 당시에는 하드웨어 한계(느린 디스크, 느린 네트워크) 때문에 비동기 IO와 높은 시스템 콜 빈도 전략이 합리적이었음
  • 그러나 현대 시스템은 NVMe SSD, 빠른 네트워크, 고성능 CPU가 보편적이고, 진짜 병목은 IO가 아닌 시스템 콜 오버헤드

시스템 콜과 모드 스위치의 비용

  • 프로그램이 파일 읽기와 같은 작업을 요청하면, user mode에서 kernel mode로 전환해야 하며, 이 과정은 고가의 CPU 사이클(1000~1500 cycles)이 소모됨
  • 패키지 설치는 기본적으로 수만~수십만 번 이상의 시스템 콜을 요구하여 실제 작업 전환 비용만으로도 수초의 CPU 시간 소모 발생
  • 예를 들어, React 및 의존성 설치시 npm은 약 100만 번, yarn은 400만 번, pnpm은 50만 번, bun은 16만 번의 시스템 콜 사용

기존 패키지 매니저와 Bun의 접근 방식 차이

  • npm, pnpm, yarn은 모두 Node.js 기반으로, 자바스크립트를 여러 추상 레이어(libuv, 이벤트 루프, 쓰레드풀, 시스템 콜 중개)로 실행해야 함
  • 이때 아규먼트 변환, 워커풀 큐, 이벤트 루프 작업 분기, futex(락 동기화) 시스템 콜 등이 누적되어, IO보다 시스템 콜 관리가 오히려 더 느려지는 결과 초래함
  • Node.js로 만든 패키지 매니저는 이런 구조적 한계 때문에 실제 네이티브에 근접한 성능을 내기 어려움

Bun: Zig로 구현한 네이티브 설치 엔진

  • Bun은 Zig 언어로 직접 시스템 콜을 호출하여, 자바스크립트 엔진/추상화 레이어를 모두 생략함
  • 예를 들어 파일 읽기는 Zig 코드에서 곧바로 openat() 시스템 콜을 실행하여 바로 데이터를 반환함
  • 따라서 수만 개의 파일을 읽는 과정이 별도의 쓰레드풀·이벤트 루프·데이터 변환 과정 없이 초고속으로 동작함
  • 벤치마크상 Bun은 초당 146,057개의 package.json을 읽을 수 있으며, Node.js는 6만개 대로 2배 이상 느림

의존성 관리 및 DNS 최적화

  • Bun은 bun install 실행시 의존성 분석과 동시에 DNS prefetch를 비동기적으로 트리거함
  • 예를 들어 macOS에서는 Apple의 비공식 async DNS API(getaddrinfo_async_start())를 사용, 쓰레드 블로킹 없이 네트워크 작업 동시 처리를 지원함
  • 기존 패키지 매니저는 libuv 쓰레드풀 기반으로 실제로는 내부적으로 블로킹 코드가 실행되어 자원 낭비 발생

패키지 매니페스트 바이너리 캐싱

  • npm 등은 매니페스트를 JSON으로 캐싱하나, Bun은 한 번 파싱한 후, 이 결과를 바이너리(.npm 파일)로 변환해 저장함
  • 문자열 중복/파싱 오버헤드 최소화, 실제 메모리에선 오프셋 계산만으로 바로 값 접근이 가능해짐(새 객체 생성, 파싱, 가비지 컬렉션 불필요)
  • ETag와 If-None-Match 헤더로 변경 점만 확인, 불필요한 데이터 파싱 없이 최신성 검증 가능
  • 벤치마크상, Bun의 캐시 설치가 npm fresh install보다도 빠름

Tarball(압축 파일) 처리 성능

  • 일반 패키지 매니저는 tarball을 스트림으로 받아, 버퍼 메모리 부족시마다 재할당·복사·리사이즈가 연속 발생함
  • Bun은 tarball 전체 수신 후 패킹 해제하며, gzip 마지막 4바이트로 언컴프레스 사이즈를 사전 파악→** 한 번만 메모리 할당**
  • libdeflate 등을 활용해 빠른 해제, 불필요한 중복 복사와 사이즈 리사이즈 모두 제거

의존성 그래프 & 데이터 구조 최적화

  • 기존 패키지 매니저는 자바스크립트 오브젝트/포인터 기반 의존성 트리를 만들어, 메모리 랜덤 분산 및 CPU 캐시 미스를 빈번하게 만듦(pointer chasing 문제)
  • Bun은 Structure of Arrays(SoA) 패턴을 적용해, 모든 패키지 및 문자열, 의존성을 큰 연속 메모리 덩어리로 저장함
    • 오프셋/길이 기반 접근으로 CPU가 한 번에 여러 패키지를 캐시 라인 단위로 읽을 수 있음(캐시 친화적 구조)
    • lockfile 역시 JSON/YAML 대신 SoA 패턴에 맞게, 문자열 중복 제거 및 순차 메모리 접근이 용이하게 저장
  • lockfile의 바이너리 형식(bun.lockb)도 실험적으로 도입했으나, Git 협업성 저하로 가독성 높은 플레인 포맷으로 전환

OS별 파일 복사 최적화

macOS

  • clonefile 사용: 통째로 디렉토리를 Copy-On-Write 방식의 한 번의 시스템 콜로 복제
  • 디스크 공간 중복 사용 최소화, 설치 속도 최대화 제공
  • clonefile 실패시 폴백으로 per-directory cloning→copyfile로 단계적 폴백

Linux

  • 하드링크 우선 시도: 새로운 파일을 생성하지 않고 기존 파일의 새로운 참조만 만듦(디스크 데이터 이동 없음)
  • 하드링크 불가시 Btrfs/XFS에서는 ioctl_ficlone으로 Copy-On-Write 적용
  • 이후에는 copy_file_range, sendfile, 그리고 마지막으로 일반 copyfile 방식으로 폴백

총평

  • Bun은 시스템 콜 최소화, 바이너리 구조, OS 최적화, 데이터 구조 개선을 통해 패키지 매니저의 전통적인 성능 한계를 뛰어넘었음
  • 이로 인해 초고속 설치와 더불어, 메모리/CPU 효율까지 모두 개선하는 장점 제공
  • 기존 Node.js 기반 매니저 대비 별도 런타임 교체 없이도 프로젝트 적용 가능(호환성 유지)
  • 실제 대규모 코드베이스에서 수분 걸리던 설치 과정을 수 밀리초~수 초 이내로 단축시키는 경험 제공
  • 시스템, 하드웨어, OS 레벨에 맞춘 맞춤 최적화의 우수사례로 연구·참고 가치가 높음

Read Entire Article