Fight the Future

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

JDK9では文字列の連結にinvokedynamicが使われることになりそうだ

JEPにあり、JDK9のターゲットになっている。

JEP 280: Indify String Concatenation

Change the static String-concatenation bytecode sequence generated by javac to use invokedynamic calls to JDK library functions. This will enable future optimizations of String concatenation without requiring further changes to the bytecode emitted by javac.

超意訳:静的な文字列連結においてjavacが生成するバイトコードシーケンスを変える。JDKライブラリの関数をinvokedynamic呼び出しを使うためだ。これにより将来的には文字列連結の最適化ができるようになる。最適化はjavacが出力するバイトコードをさらに変更することなくできるようになる。

ほほー、ラムダ式に続いて文字列連結にもindy使うことになるんですね。さっそくやってみます。まずはOpenJDKのビルド。慣れてる人はここ飛ばしてください。

$ hg clone http://hg.openjdk.java.net/jdk9/sandbox jdk9sandbox

$ cd jdk9sandbox

$ sh ./get_source.sh

$ sh ./common/bin/hgforest.sh up -r JDK-8085796-indyConcat

$ sh ./configure

$ make images

$export JAVA_HOME=~/jdk9sandbox/build/macosx-x86_64-normal-server-release/images/jdk/

$ java -version
OpenJDK Runtime Environment (build 9-internal+0-2015-12-25-171222.jyukutyo.jdk9sandbox)
OpenJDK 64-Bit Server VM (build 9-internal+0-2015-12-25-171222.jyukutyo.jdk9sandbox, mixed mode)

キモはsh ./common/bin/hgforest.sh up -r JDK-8085796-indyConcatしてリビジョンを変更することですね。

そこらへんにあったHelloWorldに文字列連結するメソッドを追加しときました。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }

    public String m(String a, int b) {
        return a + "(" + b + ")";
    }
}

さて、JDK8でjavap -vするとこうです。

  public java.lang.String m(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: new           #5                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
         7: aload_1
         8: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        11: ldc           #8                  // String (
        13: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        16: iload_2
        17: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        20: ldc           #10                 // String )
        22: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        25: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        28: areturn

StringBuilderを生成してappend()してるので、invokevirtualです。

さきほどのJDK9で同じくjavap -vします。

  public java.lang.String m(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_1
         1: iload_2
         2: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
         7: areturn

ほほー。// InvokeDynamic #0:makeConcatWithConstantsとか出ちゃってますね!素敵! これはとくにコンパイルオプションをつけていませんが、-XDstringConcat=indyWithConstantsをつけた場合も同じバイトコードです。 別の-XDstringConcat=indyというオプションをつけてコンパイルし、javap -vするとこうなります。

  public java.lang.String m(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=3, args_size=3
         0: aload_1
         1: ldc           #5                  // String (
         3: iload_2
         4: ldc           #6                  // String )
         6: invokedynamic #7,  0              // InvokeDynamic #0:makeConcat:(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)Ljava/lang/String;
        11: areturn

同じくindy使っていますが、定数である"("と")"は別になりました。indyWithConstantsでコンパイルすると定数もindyに入るようですね。定数を含まない方が読みやすそうなので、とりあえずこっちを見ていきます。

indyなのでMethodHandle探します。

#29 = MethodHandle       #6:#39         // invokestatic java/lang/invoke/StringConcatFactory.makeConcat:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

なるほど、ラムダ式のときのLambdaMetaFactoryのように文字列連結にはStringConcatFactoryクラスがあるんですね。CallSiteオブジェクトを生成するメソッドがありました。

    public static CallSite makeConcat(MethodHandles.Lookup lookup,
                                      String name,
                                      MethodType concatType) throws StringConcatException {
        if (DEBUG) {
            System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType);
        }

        return doStringConcat(lookup, name, concatType, true, null);
    }

    private static CallSite doStringConcat(MethodHandles.Lookup lookup,
                                           String name,
                                           MethodType concatType,
                                           boolean generateRecipe,
                                           String recipe,
                                           Object... constants) throws StringConcatException {
...(中略)

        MethodType mt = adaptType(concatType);

        Recipe rec = new Recipe(recipe, constants);

        MethodHandle mh;
        if (CACHE_ENABLE) {
            Key key = new Key(mt, rec);
            mh = CACHE.get(key);
            if (mh == null) {
                mh = generate(lookup, mt, rec);
                CACHE.put(key, mh);
            }
        } else {
            mh = generate(lookup, mt, rec);
        }
        return new ConstantCallSite(mh.asType(concatType));
    }

    private static MethodHandle generate(Lookup lookup, MethodType mt, Recipe recipe) throws StringConcatException {
        try {
            switch (STRATEGY) {
                case BC_SB:
                    return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.DEFAULT);
                case BC_SB_SIZED:
                    return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.SIZED);
                case BC_SB_SIZED_EXACT:
                    return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.SIZED_EXACT);
                case MH_SB_SIZED:
                    return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED);
                case MH_SB_SIZED_EXACT:
                    return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT);
                case MH_INLINE_SIZED_EXACT:
                    return MethodHandleInlineCopyStrategy.generate(mt, recipe);
                default:
                    throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
            }
        } catch (Throwable t) {
            throw new StringConcatException("Generator failed", t);
        }
    }

    private enum Strategy {
        /**
         * Bytecode generator, calling into {@link java.lang.StringBuilder}.
         */
        BC_SB,

        /**
         * Bytecode generator, calling into {@link java.lang.StringBuilder};
         * but trying to estimate the required storage.
         */
        BC_SB_SIZED,

        /**
         * Bytecode generator, calling into {@link java.lang.StringBuilder};
         * but computing the required storage exactly.
         */
        BC_SB_SIZED_EXACT,

        /**
         * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
         * This strategy also tries to estimate the required storage.
         */
        MH_SB_SIZED,

        /**
         * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
         * This strategy also estimate the required storage exactly.
         */
        MH_SB_SIZED_EXACT,

        /**
         * MethodHandle-based generator, that constructs its own byte[] array from
         * the arguments. It computes the required storage exactly.
         */
        MH_INLINE_SIZED_EXACT
    }

ConstantCallSite(ターゲットを変更できないCallSite)を返しています。MethodHandleはStrategyで生成しているようです。

今日はここまで!