Dart 2.2 발표: 더 빨라진 네이티브 코드와 Set 리터럴 지원

Dart 2.2 발표: 더 빨라진 네이티브 코드와 Set 리터럴 지원

오늘은 Dart 2 SDK의 안정 버전인 Dart 2.2를 발표해요. 이번 버전은 Dart 2에 대한 점진적인 업데이트로, AOT(사전 컴파일, ahead-of-time)로 컴파일된 네이티브 코드의 성능을 개선하고, 새로운 Set 리터럴 언어 기능을 추가했어요.

출처: Announcing Dart 2.2: Faster native code and support for set literals

본문

Flutter 개발자를 위한 Dart 성능 개선

Flutter 앱처럼 AOT로 컴파일되는 코드를 더 빠르게 만들기 위한 작업을 계속하고 있어요. Dart 2.1에서는 타입 검사(type check)의 오버헤드를 줄여서, AOT로 컴파일된 코드와 JIT(just-in-time) 컴파일로 VM에서 실행되는 코드 모두에서 타입 검사의 비용을 크게 낮췄죠.

Dart 2.2에서는 특히 AOT로 컴파일된 코드의 성능에 집중했어요. 마이크로벤치마크 기준 AOT 성능이 11~16% 개선됐어요(코드 크기는 약 1% 증가하는 대가를 치렀지만요). 이 개선은 정적 호출(static call)의 오버헤드를 줄이기 위한 여러 분기 작업의 결과예요. 이제 최적화된 AOT 코드는 PC 상대 호출(PC-relative call, 프로그램 카운터를 이용하는 방식)로 목적지를 직접 호출할 수 있어요. 그 전에는 목적지 주소를 알아내기 위해 객체 풀(object pool)을 여러 번 조회해야 했거든요. 이 최적화는 위젯을 많이 만드는 Flutter UI 코드처럼 생성자와 정적 메서드 호출이 많은 코드에서 특히 유용해요.

Dart 2.2의 Set 리터럴 언어 기능

Dart의 핵심 라이브러리(dart:core)에는 객체의 Map, List, Set을 다루기 위한 여러 컬렉션 클래스가 들어 있어요. Map은 키-값 쌍의 컬렉션이고, List는 값의 순서 있는 시퀀스라서 각 값을 인덱스로 접근할 수 있고 같은 값이 여러 번 나타날 수 있어요. Set은 순서가 없는 값의 컬렉션으로, 각 값은 한 번만 나타날 수 있고 어떤 값이 집합에 있는지를 효율적으로 확인할 수 있죠.

Dart 컬렉션은 보통 컴파일 타임 상수로 초기화돼요. 그래서 Dart는 이런 초기화를 표현하기 위한 편리한 리터럴 문법을 제공해요. Dart에서 List는 이렇게 초기화할 수 있어요.

const List<String> releases = ['Dart 2.0', 'Dart 2.1', 'Dart 2.2'];

그런데 이전에는 Dart가 리터럴 문법을 List와 Map에만 지원했어요. 그래서 Set을 초기화하려면 리스트를 거쳐야 해서 번거로웠죠.

Set<String> currencies = Set.of(['EUR', 'USD', 'JPY']);

이 코드는 번거롭고 비효율적일 뿐 아니라, 리터럴을 지원하지 않기 때문에 currencies를 컴파일 타임 상수로 만들 수 없다는 문제도 있어요. Dart 2.2에서 리터럴을 Set까지 확장하면서, 이제는 편리한 새 문법으로 Set을 초기화하고 const로 만들 수 있게 됐어요.

const Set<String> currencies = {'EUR', 'USD', 'JPY'};

Flutter 팀이 Set 리터럴을 실제로 어떻게 적용하기 시작했는지 보려면 PR #27811을 참고하세요. Set 리터럴을 사용하는 더 자세한 방법은 업데이트된 Dart 2.2 언어 둘러보기(language tour)에서 확인할 수 있어요.

Dart 2 공통 프론트엔드(CFE)로 새로운 언어 기능 만들기

Dart 언어 구현들 — Flutter가 사용하는 Dart VM, dart2js 컴파일러, Dart dev 컴파일러(dartdevc) — 은 공통의 프론트엔드를 공유해요. Dart 공통 프론트엔드(Common Front End, CFE)는 Dart 코드를 파싱하고 타입 추론을 수행하며, Dart를 백엔드 구현이 입력으로 받는 더 낮은 수준의 중간 언어로 변환해요.

Set 리터럴 기능은 CFE 덕분에 빠르게 개발할 수 있었던 언어 구성 요소의 좋은 예시예요. Set 리터럴을 파싱하고 타입 추론을 수행하는 코드는 모든 Dart 백엔드를 위해 CFE에 한 번만 구현됐어요. 게다가 백엔드가 처음에 사용할 수 있는 프론트엔드 전용 임시 구현도 만들었죠. 이 임시 구현에서는 위에서 본 non-const 버전의 currencies Set 리터럴이 컴파일 중에 다음과 같은 코드로 변환됐어요.

Set<String> currencies = Set<String>()..add('EUR')..add('USD')..add('JPY');

const Set 리터럴의 임시 구현은 조금 달라요. const Set은 조각조각 점진적으로 만들 수 없거든요. 그래서 const Map을 감싸는 private 한 unmodifiable set 클래스로 구현했어요. 이때 set의 요소가 map의 키가 돼요.

const Set<String> currencies =
   _UnmodifiableSet<String>({'EUR': null, 'USD': null, 'JPY': null});

이 unmodifiable set 클래스는 감싸고 있는 map을 이용해 Set 인터페이스의 메서드들을 구현해요.

전체적으로, Set 리터럴을 처음에는 CFE 전용 기능으로 구현할 수 있었어요. 백엔드는 처음에 CFE 구현을 쓸 수 있었고, 나중에 이 기능의 성능 측면을 더 잘 파악한 뒤 자체 네이티브 지원을 독립적으로 개발할 수 있었죠. 덕분에 백엔드는 이 기능의 성능 특성이 더 잘 이해될 때까지 네이티브 지원을 미룰 수 있었어요.

Dart 2.2 언어 명세

Dart 2는 Dart에 대한 아주 큰 업그레이드라서, 우리가 만든 모든 변경 사항에 맞춰 공식 Dart 언어 명세를 업데이트하는 데 꽤 시간이 걸렸어요. 마침내 그 작업을 마쳤고, 명세는 Dart 2.2 기준으로 최신 상태가 됐어요. 언어 명세 소스도 새 언어 저장소(language repository)로 옮기고, 지속적 통합(continuous integration)을 추가해서 Dart 언어의 향후 버전을 진화시킬 때 PDF 형식의 롤링 드래프트 명세가 자동으로 생성되도록 했어요. 2.2 버전과 롤링 Dart 2.x 명세 모두 Dart 명세 페이지에서 확인할 수 있어요.

Dart 2.2 받기

Dart SDK 2.2는 오늘 Dart 홈페이지에서 내려받을 수 있어요. Flutter 개발자라면 Dart 2.2가 이미 포함되어 있어요. (참고: 현재 Flutter master와 dev 채널은 Dart 2.2를 보고합니다. 오늘 출시된 Flutter 1.2 안정 버전은 Dart 2.1.2를 보고하는데, Dart 2.2와 동일한 기능을 가져요.)

이번엔 여기까지예요. Dart 2.2를 즐겁게 사용하시길 바랄게요!

더 알아보기