以下の内容はhttps://wrongwrong163377.hatenablog.com/entry/2025/07/05/175237より取得しました。


【jackson-module-kogera】value class関連処理をMethodHandle化するとどれ位高速化するか

jackson-module-kogera 2.19.0-beta25にて、value classシリアライズ / デシリアライズに関連する処理でMethodHandleを使うように変更しました。

github.com

この記事では、変更の効果が最低でどの程度かを確認した結果を紹介します。

value class関連処理をMethodHandle化する理由

Jacksonvalue classを違和感なく処理できるようにするため、value class関連処理は多くのリフレクション呼び出しが必要であり、リフレクション呼び出しが連続することもしばしば有るためです。

例えばデシリアライズ時は、最低でも「プライマリコンストラクタ呼び出し -> box化呼び出し」と2回のリフレクション呼び出しが必要です。
プロパティが非nullの場合、更にunbox化呼び出しも追加で必要です。

シリアライズの場合も、box化呼び出しとunbox化で2回のリフレクション呼び出しが必要です。

これらはMethodHandle化することで多くの改善が期待できます。

ベンチマーク

以下のリポジトリを使って比較を行いました。

github.com

ベンチマーク内容

MethodHandleで効果的に高速化するためには、型を明示することが重要です。
jackson-module-kogera 2.19.0-beta25では、ラップされる型がInt, Long, String, (Java)UUIDのいずれかだった場合に関して、型を明示的に扱うことによる最適化を行っています。
よって、この最適化の有無を比較する必要が有ります。

また、ベンチマーク内容は、Jacksonによる処理の量をなるべく減らし、今回の高速化に関する効果が表れやすいようにする必要が有ります。

以上を考慮し、ベンチマーク内容は以下のようにしました。

  • ベンチマークの入出力は基本的に1桁整数 + 最低限のJSON構造
  • ベンチマーク対象となる型は1プロパティのみ持たせる
  • 検証対象とするvalue classは、Intをラップしたもの(= 型明示による最適化有り)と、Shortをラップしたもの(= 型明示による最適化無し)

最適化の効果はvalue classの数が増えた分だけ高まるため、このベンチマークからは、value classに関する処理が最低でどれだけ改善されたかが分かります。

結果

8ad900dをまとめたものです。

スループット

比が1より大きいほど改善されています。

シリアライズ
10/12ケースで改善が確認できました。
最も基本的なケースであるunbox.IntBenchmark.wrapped(シンプルなvalue classをプロパティに持つクラス)に関しては、約10%のスループット向上を記録しました。

key.jsonKey.ShortBenchmark.benchmarkunbox.ShortBenchmark.directに関しては劣化が確認されましたが、それぞれ1%未満であり、誤差の範疇です。

2.19.0-beta24 2.19.0-beta25 2.19.0-beta25 / 2.19.0-beta24
key.jsonKey.IntBenchmark.benchmark 1777332.255594 1817286.155664 1.022479702
key.jsonKey.ShortBenchmark.benchmark 1831096.322124 1819773.928008 0.9938166038
key.unbox.IntBenchmark.benchmark 1548940.949888 1557133.079293 1.005288858
key.unbox.ShortBenchmark.benchmark 739703.867427 760804.578200 1.028525889
jsonValue.IntBenchmark.direct 2892679.151030 2912412.745928 1.006821909
jsonValue.IntBenchmark.wrapped 1663825.635318 1866756.436996 1.121966387
jsonValue.ShortBenchmark.direct 2811182.009216 2904066.732081 1.033041163
jsonValue.ShortBenchmark.wrapped 1670737.423967 1832761.453181 1.096977554
unbox.IntBenchmark.direct 3179525.595621 3225851.588935 1.014570096
unbox.IntBenchmark.wrapped 1874000.549577 2057832.237266 1.098095856
unbox.ShortBenchmark.direct 3137818.450200 3135692.306827 0.9993224135
unbox.ShortBenchmark.wrapped 1631790.050848 1727636.426984 1.058736953

シリアライズ
8/10ケースで改善が確認できました。
最も基本的なケースであるbyPrimaryConstructor.IntBenchmark.wrapped(シンプルなvalue classをプロパティに持つクラス)に関しては、約7%のスループット向上を記録しました。

byJsonCreator.ShortBenchmark.directbyPrimaryConstructor.IntBenchmark.directに関しては、それぞれ約4%の劣化が確認されました。
ただし、これらはvalue classを直接デシリアライズする稀なケースです。

2.19.0-beta24 2.19.0-beta25 2.19.0-beta25 / 2.19.0-beta24
KeyBenchmark._int 628928.592524 642907.263310 1.022226165
KeyBenchmark._short 536357.711456 555365.450802 1.03543855
byJsonCreator.IntBenchmark.direct 1784708.471187 1850169.260039 1.036678701
byJsonCreator.IntBenchmark.wrapped 916055.903594 958713.142722 1.046566196
byJsonCreator.ShortBenchmark.direct 1880281.625663 1809603.994811 0.9624111463
byJsonCreator.ShortBenchmark.wrapped 921136.345195 935274.268244 1.01534835
byPrimaryConstructor.IntBenchmark.direct 2147950.142659 2057745.453727 0.9580042911
byPrimaryConstructor.IntBenchmark.wrapped 995537.946778 1064727.840156 1.069500006
byPrimaryConstructor.ShortBenchmark.direct 1691792.929911 1739138.397988 1.02798538
byPrimaryConstructor.ShortBenchmark.wrapped 904488.697510 963217.354551 1.064930228

シングルショット

比が1より小さいほど改善されています。

シリアライズ
11/12ケースで劣化、劣化しなかったケースもほぼ差が有りませんでした。
最も基本的なケースであるunbox.IntBenchmark.wrappedに関して、劣化は2%未満でした。

2.19.0-beta24 2.19.0-beta25 2.19.0-beta25 / 2.19.0-beta24
key.jsonKey.IntBenchmark.benchmark 184.561424 195.296498 1.058165318
key.jsonKey.ShortBenchmark.benchmark 187.364271 194.738718 1.039358875
key.unbox.IntBenchmark.benchmark 192.471979 192.413958 0.9996985483
key.unbox.ShortBenchmark.benchmark 185.425115 186.525809 1.005936057
jsonValue.IntBenchmark.direct 172.408964 176.969350 1.02645098
jsonValue.IntBenchmark.wrapped 286.545790 295.294399 1.030531277
jsonValue.ShortBenchmark.direct 175.135014 175.543538 1.002332623
jsonValue.ShortBenchmark.wrapped 282.984258 295.457164 1.044076325
unbox.IntBenchmark.direct 175.688984 177.968913 1.012977074
unbox.IntBenchmark.wrapped 292.981319 297.085654 1.014008862
unbox.ShortBenchmark.direct 176.957803 177.437413 1.002710307
unbox.ShortBenchmark.wrapped 281.099359 288.922695 1.027831213

シリアライズ
7/10ケースで劣化、劣化しなかったケースもほぼ差が有りませんでした。
最も基本的なケースであるbyPrimaryConstructor.IntBenchmark.wrappedに関して、劣化は2%少しでした。

2.19.0-beta24 2.19.0-beta25 2.19.0-beta25 / 2.19.0-beta24
KeyBenchmark._int 281.434946 280.577312 0.9969526386
KeyBenchmark._short 277.767897 286.769142 1.032405635
byJsonCreator.IntBenchmark.direct 269.644305 270.065927 1.001563623
byJsonCreator.IntBenchmark.wrapped 305.317364 307.809560 1.008162641
byJsonCreator.ShortBenchmark.direct 270.556615 275.870427 1.019640296
byJsonCreator.ShortBenchmark.wrapped 308.366190 306.834562 0.9950330871
byPrimaryConstructor.IntBenchmark.direct 275.885608 278.743274 1.010358155
byPrimaryConstructor.IntBenchmark.wrapped 302.336269 309.165245 1.022587353
byPrimaryConstructor.ShortBenchmark.direct 272.375393 269.656569 0.9900180998
byPrimaryConstructor.ShortBenchmark.wrapped 297.234643 316.185681 1.063757837

全体を通した考察

スループットに関しては、想定通り基本的なケース全般で改善を確認できました。
特にシリアライズは10%、デシリアライズは7%程度と、Jacksonに関するその他処理が有る中ではそれなりの改善量が得られたことは良かったです。
ただし、恐らくベンチマーク負荷が軽すぎたため、型明示による最適化有無の差はそれ程確認できませんでした(これに関しては、逆に「何が何でも最適化しなければならないほどの差は無い」とも言えるでしょうか)。

シングルショットに関しては、初期化処理の量自体は増えているため、想定通りほぼ劣化となりました。
ただ、全体を通しても劣化幅は10%未満であるため、それ程酷くならなかった点は安心しました。

data classとの差について

data classと比較した際のスコアは、残念ながらMethodHandle化後も大きいままでした。

github.com

補足

経験上、恐らくLambdaMetaFactoryを使った方が高速ですが、以下の理由からJacksonではLambdaMetaFactoryを使わない方針となっているため、試していません。

github.com

おまけ

ベンチマーク結果は以下に格納してあります。
一応比較用テンプレートも用意しているため、ローカルでの実行結果比較も可能です。

drive.google.com




以上の内容はhttps://wrongwrong163377.hatenablog.com/entry/2025/07/05/175237より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14