Microsoft OpenJDKのエスケープ解析改善を試したら割と効いてる

MicrosoftのOpenJDKにエスケープ解析の改善が入ってるってことで試してみました。
https://www.infoq.com/news/2023/02/microsoft-openjdk-feature/

Microsoftのアナウンスはこちら。
https://devblogs.microsoft.com/java/microsoft-build-of-openjdk-october-2022-psu-release/

エスケープ解析は、雑にいうと、メソッド内で生成されてそのメソッド内だけで使われるようなオブジェクトについて、ヒープに確保するのではなくてフィールドをローカル変数に分解して処理をするような最適化です。
正しくは、そいういう最適化が行えることを発見する解析のことだけど。

基本的にはメソッド呼び出しを埋め込み処理に変換するインライン化とあわせて威力を発揮します。

例えばこんな感じのクラスがあるとして。

record Point(int x, int y) {
  Point add(Point other) {
    return new Point(x + other.x, y + other.y);
  }
}

こんな感じの処理があるとする。

void some() {
  var p = new Point(3, 8);
  var next = p.add(new Point(3, 3));
  printf("%d,%d - %d,%d%n", p.x(), p.y(), next.x(), next.y());
}

ここでaddメソッドをインライン化

void some() {
  var p = new Point(3, 8);
  Point other = new Point(3, 3);
  var next = new Point(p.x + other.x, p.y + other.y);
  printf("%d,%d - %d,%d%n", p.x(), p.y(), next.x(), next.y());
}

そうすると、このpothernextsomeメソッドから出ていない、エスケープしていないので、ヒープに置いてメソッド処理の終了からしばらくしてGCするよりも、メソッド終了とともに開放したほうがいいってなります。 そうするとスタックに置こうということになるのだけど、オブジェクトをスタックに置くというのは今のところめんどい。であればローカル変数に分解しようというのがscalar replacementです。

void some() {
  int p_x = 3;
  int p_y = 8;
  int other_x = 3;
  int other_y = 3;
  int next_x = p_x + other_x
  int next_y = p_y + other_y;
  printf("%d,%d - %d,%d%n", p_x, p_y, next_x, next_y);
}

これはすでにJava 8でも存在する機能なのだけど、object allocation mergeを簡素化することでscalar replacementを増やす改善ということらしい。mergeがなにかよくわからないけど、メソッド呼び出しに関わることな雰囲気。
https://github.com/openjdk/jdk/pull/9073

で、試してみました。ベンチマークによくつかっているレイトレ。3次元座標をあらわすVecをnewしまくるので、エスケープ解析が効くと速くなるアプリケーションです。

https://github.com/kishida/smallpt4j/blob/original/src/main/java/naoki/smallpt/SmallPT.java

このSAMPLES_DEFAULTは100にしています。またfastmathは使わず、java.lang.Mathを使っています。

エスケープ解析の改善を行うには次のオプションを指定します。

-XX:+UnlockExperimentalVMOptions -XX:+ReduceAllocationMerges

実行するとこんな画像が生成されます。

結果としてはこんな感じ。単位は秒。値が小さいほど性能が高いです。
RAMがReduceAllocationMergesを指定したものです。

ということで、12.65秒から12.46秒に改善、1.5%くらい速くなっていて、Microsoftのブログにある2%のスループット改善に近い値がでました。

だいたいこういう改善は、結局あまり手元のベンチマークに現れないことが多いのだけど、今回は実行する感触として速くなってるなーというのがありました。