iBetter Books
수정

Ch 01. AOT 컴파일과 네이티브 실행 파일

Dart는 두 가지 방식으로 실행됩니다. 개발 중에는 JIT(Just-In-Time) 컴파일로 빠른 핫 리로드를 지원하고, 배포 시에는 AOT(Ahead-Of-Time) 컴파일로 네이티브 바이너리를 만듭니다.

dart run으로 실행하는 것과 dart compile exe로 만든 바이너리를 실행하는 것은 성능 차이가 큽니다. 특히 CLI 도구나 서버의 시작 시간이 크게 달라집니다.

JIT vs AOT 비교

JIT 방식은 코드를 실행하면서 그때그때 컴파일합니다. 처음 실행할 때는 느리지만, 핫스팟(자주 실행되는 코드)을 런타임에 최적화합니다. dart run 명령이 JIT 방식입니다.

AOT 방식은 실행 전에 모든 코드를 네이티브 기계어로 변환합니다. 시작 시간이 매우 빠르고 런타임 오버헤드가 없습니다. 단, 런타임 최적화는 불가능합니다.

# JIT 실행 (개발용)time dart run bin/todo_server.dart# 실행 시작까지 약 0.5~1초# AOT 컴파일 후 실행 (배포용)dart compile exe bin/todo_server.dart -o todo_servertime ./todo_server# 실행 시작까지 약 0.05초 이하

CLI 도구의 경우 이 차이가 사용자 경험에 직접 영향을 줍니다. dart run으로 실행하면 매번 0.5~1초 기다려야 하지만, AOT 바이너리는 즉시 응답합니다.

dart compile 명령 옵션

Dart는 여러 컴파일 타겟을 제공합니다.

# 네이티브 실행 파일 (가장 일반적)dart compile exe bin/main.dart -o build/app# AOT 스냅샷 (Dart Runtime 필요, 파일 크기 작음)dart compile aot-snapshot bin/main.dart -o build/app.aot# JIT 스냅샷 (Dart Runtime 필요, 핫 리로드 가능)dart compile jit-snapshot bin/main.dart -o build/app.jit# JavaScript (웹 서버 등)dart compile js bin/main.dart -o build/app.js# 커널 바이트코드dart compile kernel bin/main.dart -o build/app.dill

가장 많이 쓰이는 것은 exe입니다. Dart Runtime이 없는 환경(Docker 베이스 이미지, 다른 서버)에서도 실행할 수 있는 단일 바이너리를 만듭니다.

실행 파일 크기 확인

dart compile exe bin/todo_server.dart -o build/todo_server# 파일 크기 확인ls -lh build/todo_server# 대략 5~15MB (포함된 라이브러리에 따라 다름)# 의존성 없는 간단한 프로그램dart compile exe bin/hello.dart -o build/hellols -lh build/hello# 약 5MB (Dart Runtime이 내장됨)

Go언어가 만드는 바이너리(보통 5MB 이하)보다 크지만, 별도 런타임 설치 없이 실행할 수 있다는 점은 같습니다.

PART 04 file_organizer를 AOT 컴파일

PART 04에서 만든 CLI 도구를 배포용 바이너리로 만들어 봅니다.

cd file_organizer# 빌드 디렉토리 생성mkdir -p build# AOT 컴파일dart compile exe bin/file_organizer.dart -o build/file_organizer# 실행 테스트./build/file_organizer --help./build/file_organizer --source ~/Downloads --dry-run

바이너리 하나만 복사하면 Dart가 설치되지 않은 환경에서도 실행됩니다. PATH에 추가하거나 /usr/local/bin/에 복사하면 전역 명령어처럼 사용할 수 있습니다.

# macOS/Linux에서 전역 설치sudo cp build/file_organizer /usr/local/bin/file_organizer --help  # 어디서든 실행 가능

todo_server AOT 컴파일

dart_frog 서버를 AOT 컴파일합니다. dart_frog에는 빌드 명령이 있습니다.

cd todo_server# dart_frog 빌드 (AOT 컴파일 포함)dart_frog build# build/ 폴더에 결과물 생성ls build/# bin/  pubspec.yaml  pubspec.lock  ...# 빌드된 서버 실행cd builddart compile exe bin/server.dart -o server./server

dart_frog의 dart_frog build 명령은 서버 코드를 하나의 bin/server.dart로 번들링합니다. 그 후 dart compile exe로 네이티브 바이너리를 만듭니다.

크로스 컴파일의 한계

AOT 컴파일은 현재 실행 중인 OS/아키텍처에 맞는 바이너리를 생성합니다. macOS에서 컴파일하면 macOS용 바이너리, Linux에서 컴파일하면 Linux용 바이너리가 만들어집니다. Windows 서버에 배포하려면 Windows에서 컴파일해야 합니다.

이 문제를 해결하는 가장 깔끔한 방법이 Docker입니다. Linux 컨테이너 안에서 컴파일하면 항상 Linux 바이너리가 만들어집니다. 다음 챕터에서 이를 다룹니다.

바이너리 크기 최적화

Tree shaking은 AOT 컴파일 시 자동으로 적용됩니다. 사용되지 않는 코드는 최종 바이너리에 포함되지 않습니다. 별도로 설정하지 않아도 됩니다.

추가로 할 수 있는 최적화는 불필요한 의존성을 제거하는 것입니다. dart pub deps로 의존성 트리를 확인하고, 실제로 사용하지 않는 패키지가 있다면 pubspec.yaml에서 제거합니다.

# 의존성 트리 확인dart pub deps# 사용하지 않는 패키지 찾기dart pub outdated --no-dev-dependencies

이번 챕터 정리

  • JIT는 개발 편의, AOT는 배포 성능에 최적화된 컴파일 방식입니다.
  • dart compile exe로 Dart Runtime이 필요 없는 단일 실행 파일을 만들 수 있습니다.
  • dart_frog는 dart_frog build로 서버 코드를 번들링합니다.
  • AOT 컴파일은 현재 OS 대상이므로 Docker를 이용해 Linux 바이너리를 만드는 것이 배포에 적합합니다.

다음 챕터에서는 Docker로 Dart 서버를 컨테이너화하고 배포합니다.