Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

Graalでのロック粗粒化

前回はGraalでの大域的値番号付けでした。

jyukutyo.hatenablog.com

こちらの記事に沿ってGraalの理解を深めます。

Understanding How Graal Works - a Java JIT Compiler Written in Java

今回はロック粗粒化(Lock coarsening)です。同一のモニタに対して続けて2回同期化しているコードがあるとします。直接そういうコードは書かないでしょうが、インライン化の結果として実際にこうなる場合があります。

void workload() {
  synchronized (monitor) {
    counter++;
  }
  synchronized (monitor) {
    counter++;
  }
}

de-sugar(脱糖)するとこんなイメージです。

void workload() {
  monitor.enter();
  counter++;
  monitor.exit();
  monitor.enter();
  counter++;
  monitor.exit();
}

exit()してすぐenter()するので、最適化としてはモニタに入るのを1回だけにすればいいわけです。

void workload() {
  monitor.enter();
  counter++;
  counter++;
  monitor.exit();
}

これがロック粗粒化です。

Graalでのロック粗粒化

前回書きましたが、Graalのグラフを変更する処理はorg.graalvm.compiler.phases.Phaseインタフェースの実装クラスです。ロック粗粒化はorg.graalvm.compiler.phases.common.LockEliminationPhaseクラスです。

    @Override
    protected void run(StructuredGraph graph) {
        for (MonitorExitNode monitorExitNode : graph.getNodes(MonitorExitNode.TYPE)) {
            FixedNode next = monitorExitNode.next();
            if ((next instanceof MonitorEnterNode || next instanceof RawMonitorEnterNode)) {
                // should never happen, osr monitor enters are always direct successors of the graph
                // start
                assert !(next instanceof OSRMonitorEnterNode);
                AccessMonitorNode monitorEnterNode = (AccessMonitorNode) next;
                if (GraphUtil.unproxify(monitorEnterNode.object()) == GraphUtil.unproxify(monitorExitNode.object())) {
                    /*
                     * We've coarsened the lock so use the same monitor id for the whole region,
                     * otherwise the monitor operations appear to be unrelated.
                     */
                    MonitorIdNode enterId = monitorEnterNode.getMonitorId();
                    MonitorIdNode exitId = monitorExitNode.getMonitorId();
                    if (enterId != exitId) {
                        enterId.replaceAndDelete(exitId);
                    }
                    GraphUtil.removeFixedWithUnusedInputs(monitorEnterNode);
                    GraphUtil.removeFixedWithUnusedInputs(monitorExitNode);
                }
            }
        }
    }

MonitorExitNodeを探し、あればMonitorExitNodeのnext()がMonitorEnterNodeかを調べます。で、GraphUtil.removeFixedWithUnusedInputsで不要なMonitorEnterNodeとMonitorExitNodeを削除します。先ほどの内容通りのことを実装しています。厳密にはモニタの対象が同じでも、モニタIDが異なる場合があるのでその場合はまず同一のIDに置き換えるケースがあります。

ロック粗粒化自体は単純ですが、その結果さらなる最適化に取り組めます。

void workload() {
  monitor.enter();
  counter++;
  counter++;
  monitor.exit();
}

counter++が2回あるので、

void workload() {
  monitor.enter();
  counter += 2;
  monitor.exit();
}

IGVで見る

ロック粗粒化をIGVで見ます。サンプルとして以下のコードを実行します。

class Demo4 {

    int counter;

    public static void main(String[] args) {
        Demo4 d = new Demo4();
        while (true) {
            d.workload();
        }
    }

    void workload() {
        synchronized (this) {
            counter++;
        }
        synchronized (this) {
            counter++;
        }
    }
}
$ java \
--module-path=/Users/jyukutyo/code/graal/sdk/mxbuild/modules/org.graalvm.graal_sdk:/Users/jyukutyo/code/graal/truffle/mxbuild/modules/com.oracle.truffle.truffle_api.jar \
--upgrade-module-path=/Users/jyukutyo/code/graal/compiler/mxbuild/modules/jdk.internal.vm.compiler \
-XX:+UnlockExperimentalVMOptions \
-XX:+EnableJVMCI \
-XX:+UseJVMCICompiler \
-XX:-TieredCompilation \
-XX:+PrintCompilation \
-XX:CompileOnly=Demo4::workload \
-XX:CompileCommand=quiet \
-Dgraal.Dump \
Demo4

Javaバイトコードをパースしたあとでは、MonitorEnterとExitが2回あります。

f:id:jyukutyo:20171129123133p:plain

各フェーズがapplyされたあとでは、MonitorEnter(RawMonitorEnter)とExitが1回となり、+も2回ではなくC(2)を1回+するだけとなっています。

f:id:jyukutyo:20171129123154p:plain