⚡️

@pragma('vm:prefer-inline') の効果を検証してみた

2026/09/25に公開

@pragma('vm:prefer-inline') の効果を検証してみた

はじめに

便利関数系のライブラリを眺めていると、@pragma('vm:prefer-inline') というアノテーションを見かけます。
この記事では、これが実際に何をするのか、そして本当に効果があるのかを、軽量なベンチマークで確かめます。

調査内容

ドキュメントを確認すると以下のようなことが分かりました。

  1. @pragma('vm:prefer-inline')
    • Dart VMに対して、このメソッドをインライン化することを推奨する
    • メソッド呼び出しのオーバーヘッドを削減
    • 特に小さな関数で効果的
  2. @pragma('dart2js:tryInline')
    • dart2jsコンパイラに対して、JavaScriptへの変換時にこのメソッドのインライン化を試みるよう指示する
    • Webアプリケーションのパフォーマンスを向上
    • 生成されるJavaScriptコードのサイズを最適化
  3. @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で編集を提案