@pragma('vm:prefer-inline') の効果を検証してみた
はじめに
便利関数系のライブラリを眺めていると、@pragma('vm:prefer-inline') というアノテーションを見かけます。
この記事では、これが実際に何をするのか、そして本当に効果があるのかを、軽量なベンチマークで確かめます。
調査内容
ドキュメントを確認すると以下のようなことが分かりました。
@pragma('vm:prefer-inline')- Dart VMに対して、このメソッドをインライン化することを推奨する
- メソッド呼び出しのオーバーヘッドを削減
- 特に小さな関数で効果的
@pragma('dart2js:tryInline')- dart2jsコンパイラに対して、JavaScriptへの変換時にこのメソッドのインライン化を試みるよう指示する
- Webアプリケーションのパフォーマンスを向上
- 生成されるJavaScriptコードのサイズを最適化
@pragma('vm:never-inline')- Dart VM に対して、このメソッドをインライン化しないよう指示する
- デバッグやスタックトレースの把握がしやすくなる
- 呼び出しコストが増える
ドキュメントは以下のとおりです。
- VM-Specific Pragma Annotations
https://dart.googlesource.com/sdk/+/2f5a888/runtime/docs/pragmas.md
- Pragma Annotations understood by dart2js
https://dart.googlesource.com/sdk/+/2f5a888/pkg/compiler/doc/pragmas.md
計測環境
@pragma('vm:prefer-inline')を付けることで実際に効果があるのか軽量なベンチマークで計測してみます。
実行環境は以下のとおりです。
- MacBook Pro 14 インチ 2023
- Apple M3 Pro
- 12 Core CPU
- 36 GB Unified Memory
- macOS Sequoia 15.7.1
$ dart --version
Dart SDK version: 3.7.2 (stable) (Tue Mar 11 04:27:50 2025 -0700) on "macos_arm64"
dependencies:
benchmark_harness: ^2.3.1
共通のベンチマーク骨格:
import 'package:benchmark_harness/benchmark_harness.dart';
abstract class InlineBase extends BenchmarkBase {
InlineBase(String name) : super(name);
int result = 0;
int body(int i);
int count = 1000;
@override
void run() {
result = 0;
for (var i = 0; i < count; i++) {
result += body(i);
}
}
}
class Bench extends InlineBase {
Bench() : super('bench');
@override
int body(int i) => fastBench(i);
}
void main() {
final instance = Bench();
instance.report();
print(instance.result);
}
- JIT (
dart run) と AOT (dart compile exe) の両方を検証します。 - 生成されたバイナリのファイルサイズ(16B 粒度で丸め)と SHA-256 先頭 7 桁も記録します。
- 数値は代表値です。マシンやフラグ、最適化の都合で再現にはばらつきが出ます。
ケース1:単純な算術関数
int fastBench(int x) {
return (x * 31) ^ 0x9e3779b9;
}
| 何も書かない | vm:prefer-inline |
vm:never-inline |
|
|---|---|---|---|
| JIT | 5.982 μs | 5.919 μs | 23.595 μs |
| AOT | 4.793 μs | 4.777 μs | 8.052 μs |
| ファイルサイズ | 5,428,560 B | 5,428,576 B | 5,428,576 B |
| SHA-256 | ececb25 |
42d91f6 |
2c9b9ff |
prefer-inline は僅かに早いみたいです。
never-inline はかなり遅いことが分かりました。
ケース2:小関数を複数回呼ぶ
int fastBench(int x) {
return fastBench1(x) + fastBench1(x) + fastBench1(x);
}
int fastBench1(int x) {
return (x * 31) ^ 0x9e3779b9;
}
| 何も書かない | vm:prefer-inline |
vm:never-inline |
|
|---|---|---|---|
| JIT | 8.365 μs | 8.201 μs | 72.851 μs |
| AOT | 8.084 μs | 5.440 μs | 24.039 μs |
| ファイルサイズ | 5,428,560 B | 5,428,576 B | 5,428,576 B |
| SHA-256 | 5df70b4 |
07ae723 |
5cbe9a7 |
AOT では prefer-inline の効果がかなり大きいみたいです。
JIT でもわずかに早くなっています。
ケース3:再帰(フィボナッチ)
フィボナッチはコストが大きいので、1 回だけ 20 まで計算するように変更。
int fastBench(int x) {
return (x <= 1) ? x : fastBench(x - 1) + fastBench(x - 2);
}
class Bench extends InlineBase {
Bench() : super('bench');
@override
int count = 1;
@override
int body(int i) => fastBench(20);
}
| 何も書かない | vm:prefer-inline |
vm:never-inline |
|
|---|---|---|---|
| JIT | 386.685 μs | 204.926 μs | 383.895 μs |
| AOT | 320.442 μs | 167.085 μs | 320.395 μs |
| ファイルサイズ | 5,428,560 B | 5,428,576 B | 5,428,576 B |
| SHA-256 | f152db9 |
b9aa8a7 |
4eabf85 |
prefer-inline の効果が非常に大きいようです。
まとめ
本当にパフォーマンスが向上するのか疑っていましたが、改善するようです!
特に頻繁に呼び出される関数などでは大きく改善するようです。
GitHubで編集を提案