ところでJavaのRaw Sring Literalsはどうなってるの?

Java12に入るとされたRaw String Literalsですが、結局は仕様から落とされました。
理由としては、貴重なクオート文字であるバッククオートをそんな簡単に使っていいの?というのが主題でした。
Raw Stringの議論では、インデントの扱いをどうするかが主で、区切り文字については あまり議論されてなかった気もします。


というところで、新年に入ってBrian Goetz氏が議論を再開しようと呼びかけています。
Raw string literals -- restarting the discussion


James Laskey氏が論点のまとめを出しています。ダイアグラムがわかりやすい。
Enhancing Java String Literals Round 2
http://cr.openjdk.java.net/~jlaskey/Strings/RTL2/index.html

  • 複数行とRawとどっちが大事?→複数行
  • 区切り文字には何を使う?→ダブルクオート
  • クオート文字を使うときは?→エスケープシーケンス


といった感じ。"""になりそうな雰囲気


で、ちょうどSwift5もRaw Stringを導入してるところで、その資料が共有されました。
Fwd: Raw string literals: learning from Swift


Swiftyかどうか、という基準いいですね。
swift-evolution/0200-raw-string-escaping.md at master · apple/swift-evolution


という感じで、調査から入ってる段階なので、まだまだ時間はかかりそう。Java 14ですかね。
実装に入ってしまえば時間はかからない気もするけど。

Truffle言語で関数呼び出しを実装する

Truffleを使って言語を実装してみたのだけど、やはり関数呼び出しもやりたい。
Truffleでの言語実装を最小手数ではじめる - きしだのはてな
簡易Truffle言語に変数を実装する - きしだのはてな

とりあえず関数

今回、関数は埋め込みで呼び出しだけ行ってみます。ということで、とりあえずランダムを返す関数を実装。

public abstract class RandNode extends MathNode {
    static Random rand = new Random();

    @Specialization
    public long rnd() {
        return rand.nextInt(10);
    }
}


ちなみに、native-imageするときstaticイニシャライザはコンパイル時に実行されるので、なにも考えずにネイティブイメージを作ると乱数が固定されて毎回同じ値を返します。
--delay-class-initialization-to-runtime=RandNodeをつける必要があります。
Understanding Class Initialization in GraalVM Native Image Generation

CallTargetを保持する

関数としてはCallTargetが欲しいので、CallTargetを保持するクラスを作ります。

class MathFunction {
    private final RootCallTarget callTarget;

    public MathFunction(RootCallTarget callTarget) {
        this.callTarget = callTarget;
    }    
}


CallTargetを作るときにRootNodeが必要になるのだけど、前回作ったものは変数定義を入れてしまったので、改めてRootNodeを作っておきます。

class FuncRootNode extends RootNode {
    MathNode function;

    public FuncRootNode(TruffleLanguage<?> language, FrameDescriptor frameDescriptor,
            MathNode function) {
        super(language, frameDescriptor);
        this.function = function;
    }

    @Override
    public Object execute(VirtualFrame frame) {
        return function.executeGeneric(frame);
    }
}


そしたら、こんな感じでパースのときにCallTargetを作って保持しておきます。

static MathFunction RAND_FUNC;
MathFunction createBuiltin(FrameDescriptor frame) {
    if (RAND_FUNC == null) {
        RandNode rand = RandNodeGen.create();
        RAND_FUNC = new MathFunction(Truffle.getRuntime().createCallTarget(
                new FunctionNodes.FuncRootNode(this, frame, rand)));
    }
    return RAND_FUNC;
}

実際には関数名->関数オブジェクトのMapに保持することになると思います。

呼び出し

呼び出しは、CallTargetのcallメソッドを呼び出すと行えます。

class MathInvokeNode extends MathNode {
    MathFunction function;

    public MathInvokeNode(MathFunction function) {
        this.function = function;
    }

    @Override
    Object executeGeneric(VirtualFrame frame) {
        return function.callTarget.call();
    }
}

今回はつかってないけど引数があるときはcallの引数で渡します。受け取り側では、VirtualFrameのgetArguments()で。


パースのときに数値以外は変数として扱うようにしたのだけど、randと書くと乱数関数を呼び出すことにします。

    MathNode parseNode(FrameDescriptor frame, String value) {
        try {
            return LongNode.of(value);
        } catch (NumberFormatException ex) {
            if ("rand".equals(value)) {
                return new MathInvokeNode(createBuiltin(frame));


これでこんな感じで動くようになります。

String exp = "12+34+56+aa+rand";
MathCommand.main(new String[]{exp});

呼び出しの最適化

さて、メソッド呼び出しを効率化しましょう。
IndirectCallNode/DirectCallNodeを使います。ここではDirectCallNodeを。

static class InvokeNode extends MathNode {
    DirectCallNode callNode;

    public InvokeNode(MathFunction function) {
        callNode = Truffle.getRuntime()
                .createDirectCallNode(function.callTarget);
    }

    @Override
    Object executeGeneric(VirtualFrame frame) {
        return callNode.call(new Object[0]);
    }
}

これでいいんだけど、実際に言語を作るとき、これだと再帰関数ではこの時点ではcallTargetが準備できてなかったりしてうまういかないことがあります。
なので遅延処理することになります。

static class InvokeNode extends MathNode {
    MathFunction function;
    DirectCallNode callNode;

    public InvokeNode(MathFunction function) {
        this.function = function;
    }

    @Override
    Object executeGeneric(VirtualFrame frame) {
        if (callNode == null) {
            callNode = Truffle.getRuntime().createDirectCallNode(function.callTarget);
        }
        
        return callNode.call(new Object[0]);
    }
}

けど、これ動くには動くけど遅くなるしGraalVMで動かすと文句いわれる。
ということで、@CompilationFinalをつけます。そして値変更時にCompilerDirectives.transferToInterpreterAndInvalidate()でGraalコンパイラに教えてあげる。

static class MathInvokeNode extends MathNode {
    MathFunction function;
    @CompilationFinal
    DirectCallNode callNode;

    public MathInvokeNode(MathFunction function) {
        this.function = function;
    }

    @Override
    Object executeGeneric(VirtualFrame frame) {
        if (callNode == null) {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            callNode = Truffle.getRuntime().createDirectCallNode(function.callTarget);
        }
        
        return callNode.call(new Object[0]);
    }
}


今回のコードだと処理速度に影響はないけど、実際の言語実装したときに10%~30%くらい速くなった。

Truffle言語をGraalVMで動かす

Truffleを使って実装した言語をGraalVMで動かす方法、なかなか難しい。
まとめると、言語実装のクラスパスはtruffle.class.path.appendで追加して、言語利用側は普通のクラスパス、ということでした。


ということで、言語実装をlangに、起動プログラムをlauncherに置く感じにします。

sample
├lang
│  └TruffleSample.java
└launcher
    └Launcher.java


言語実装はこんな感じに。指定した文字列にhelloを付け加えるだけの言語です。

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.RootNode;
import org.graalvm.polyglot.Context;

@TruffleLanguage.Registration(name = "Minimum", id = "mini",
        defaultMimeType = TruffleSample.MIME, characterMimeTypes = TruffleSample.MIME)
public class TruffleSample extends TruffleLanguage<Object>{
    static final String MIME = "application/x-mini";

    @Override
    protected CallTarget parse(ParsingRequest request) throws Exception {
        String text = request.getSource().getCharacters().toString();
        return Truffle.getRuntime().createCallTarget(new RootNode(this){
            @Override
            public Object execute(VirtualFrame frame) {
                return "Hello " + text;
            }
        });
    }
    
    @Override
    protected Object createContext(Env env) {
        return new Object();
    }

    @Override
    protected boolean isObjectOfLanguage(Object object) {
        return false;
    }
}


こんな感じでコンパイル。$GHにGraalVMのパスが設定されてるとします。truffle-api.jarとtruffle-dsl-processor.jarをクラスパスに追加する必要があります。通常のJDKコンパイルする場合にはgraal-sdk.jarも必要になりますが、GraalVMには含まれているので不要です。

sample$ cd lang
lang$ $GH/bin/javac -cp $GH/jre/lib/truffle/truffle-api.jar:$GH/jre/lib/truffle/truffle-dsl-processor.jar TruffleSample.java


そうするとlanguageというファイルもできています。

lang$ cat language
#Generated by com.oracle.truffle.dsl.processor.LanguageRegistrationProcessor
#Fri Jan 04 15:43:28 GMT 2019
language1.characterMimeType.0=application/x-mini
language1.className=TruffleSample
language1.defaultMimeType=application/x-mini
language1.id=mini
language1.implementationName=
language1.interactive=true
language1.internal=false
language1.name=Minimum
language1.version=inherit


このファイルはMETA-INF/truffleに入ってる必要があります。mavenでjarを作ると適切なフォルダに作成されるのですが、javacでコンパイルするとそうならないので、自力で移動します。

lang$ mkdir META-INF
lang$ mkdir META-INF/truffle
lang$ mv language META-INF/truffle


起動用のプログラムはこんな感じ。

import org.graalvm.polyglot.Context;
public class Launcher {
    public static void main(String... args) {
        Context ctx = Context.create("mini");
        System.out.println(ctx.eval("mini", "test"));
    }
}


org.graalvm.polyglotパッケージのあるgraal-sdk.jarはGraalVMに含まれてるので、クラスパスを追加する必要はありません。

lang$ cd ../launcher
launcher$ $GH/bin/javac Launcher.java


そしたら上のフォルダに移動して実行。-Dtruffle.class.path.appendで言語のパス、-cpで起動プログラムのパスを指定しています。

launcher$ cd ..
sample$ $GH/bin/java -Dtruffle.class.path.append=lang -cp launcher Launcher
Hello test


動きました。

簡易Truffle言語に変数を実装する

Truffleで足し算してみたけど、やっぱちょっと変数を実装しておきたい。
Truffleでの言語実装を最小手数ではじめる - きしだのはてな
ということで、変数を追加するんだけど、ここでは変数定義はせずに埋め込み変数みたいなものを実装してみます。

変数登録

今回はMathRootNodeでのexecuteのときに変数を登録します。変数名はFrameSlotで表します。FrameSlotはFrameDescriptorからとってきます。このとき、FrameSlotに変数の値の型も設定しておきます。

static class MathRootNode extends RootNode {
    ...

    @Override
    public Object execute(VirtualFrame frame) {
        setup(frame);
        return body.executeGeneric(frame);
    }

    void setup(VirtualFrame frame) {
        final FrameDescriptor desc = frame.getFrameDescriptor();
        FrameSlot slotAa = desc.findOrAddFrameSlot("aa");
        desc.setFrameSlotKind(slotAa, FrameSlotKind.Long);
        frame.setLong(slotAa, 123);            
    }
}

言語実装するときは、これを変数割り当てノードなどで実装します。

変数呼び出し

変数呼び出しノードを作ります。変数呼び出しではVirtualFrameにFrameSlotを指定してgetLongすればいいんですが、例外をにぎりつぶす便利メソッドgetLongSafeがあるのでこれを使います。

@NodeField(name = "slot", type = FrameSlot.class)
public static abstract class VariableNode extends MathNode {
    abstract FrameSlot getSlot();
    
    @Specialization
    long readLong(VirtualFrame vf) {
        return FrameUtil.getLongSafe(vf, getSlot());
    }       
}

変数呼び出しノードの登録

最後にMathLangでのパース時にこの変数ノードを使うようにします。整数のパースに失敗したら変数ということにしておきます。

MathNode parseNode(FrameDescriptor frame, String value) {
    try {
        return LongNode.of(value);
    } catch (NumberFormatException ex) {
        return VariableNodeGen.create(frame.findOrAddFrameSlot(value));
    }
}


parseメソッドも書き換えておきます。

FrameDescriptor frame = new FrameDescriptor();

MathNode node = parseNode(frame, nums[nums.length - 1]);
for (int i = nums.length - 2; i >= 0; --i) {
    node = MathNodesFactory.AddNodeGen.create(parseNode(frame, nums[i]), node);
}
MathRootNode root = new MathRootNode(this, frame, node);

実行

ということで変数を使ってみます。

public static void main(String[] args) {
    String exp = "12+34+aa";
    Context cont = Context.create("mathlang");
    System.out.println(cont.eval("mathlang", exp));
}


できました!

$ ./me
169

Truffleでの言語実装を最小手数ではじめる

あけましておめでとうございます。
ということで、Truffleで言語実装したい気分なので、まずはJyukutyoの数式処理から始めることにしました。
オレオレJVM言語を作ろう! How to create a new JVM language #Graal #Truffle - Fight the Future


けど、APIがだいぶ変わってるようでそのままではできず、あとAntlr使っているのでTruffleのみの部分を切り離して試してみました。
足し算だけの式言語をつくります。
https://github.com/kishida/simplest_truffle_expr


簡易Truffle言語に変数を実装する - きしだのHatena
Truffle言語で関数呼び出しを実装する - きしだのHatena
Truffle言語をGraalVMで動かす - きしだのHatena

依存関係

truffle-apiとtruffle-dsl-processorが必要です。truffle-dsl-processorアノテーション処理をするだけなので、実行時には不要なはず。
GraalVMで動かす場合はどちらも不要だと思うけど、OpenJDKなどで動かすにはtruffle-apiが必要です。

<dependency>
    <groupId>org.graalvm.truffle</groupId>
    <artifactId>truffle-api</artifactId>
    <version>1.0.0-rc9</version>
</dependency>
<dependency>
    <groupId>org.graalvm.truffle</groupId>
    <artifactId>truffle-dsl-processor</artifactId>
    <version>1.0.0-rc9</version>
    <scope>provided</scope>
</dependency>    


GraalVMで動かす場合、実行時にtruffle-api.jarを含めるとGraalVMが持ってるクラスと競合してClassCastExceptionになります。しかし含めないとClassNotFound...動かし方がわからない・・・
※2019/1/4 追記。やっとわかった。
Truffle言語をGraalVMで動かす - きしだのはてな

準備

まずは言語で使う型を@TypeSystemを使って登録します。空のクラスにアノテーションを指定します。

@TypeSystem(long.class)
static abstract class MathTypes {
}


言語のASTのベースとなるクラスを作ります。これはTruffleのNodeクラスを継承します。このとき@TypeSystemReferenceで先ほど型を登録したクラスを指定しておきます。
メソッドとしてVirtualFrameを受け取るexecuteGenericというメソッドを登録しておきます。executeXxxで型ごとの処理を書けるっぽい。すべての型を処理する場合はexecuteGeneric

@TypeSystemReference(MathTypes.class)
static abstract class MathNode extends Node {
    abstract Object executeGeneric(VirtualFrame frame);
}

式のASTを作る

今回はlongだけ使うので、longリテラル用のノードを作ります。longを処理するためのexecuteLongメソッドを用意しておきます。
@NodeInfoはノード情報を持たせるアノテーションです。

@NodeInfo(shortName = "value")
static class LongNode extends MathNode {
    private long value;

    private LongNode(long value) {
        this.value = value;
    }

    static LongNode of(String v) {
        return new LongNode(Long.parseLong(v.trim()));
    }

    long executeLong(VirtualFrame frame) {
        return value;
    }

    @Override
    Object executeGeneric(VirtualFrame frame) {
        return value;
    }
}


そして足し算ノード。左項と右項を保持するフィールドを、@NodeChildとして持たせてます。また、実際の足し算はaddメソッドで定義しますが、型を限定する処理には@Specializationをつけるっぽい。

@NodeInfo(shortName = "+")
@NodeChild("leftNode")
@NodeChild("rightNoode")
public static abstract class AddNode extends MathNode {
    @Specialization
    public long add(long left, long right) {
        return left + right;
    }

    public Object add(Object left, Object right) {
        return null;
    }
}


アノテーションプロセッサによって、AddNodeGenというクラスが生成されて、実際にaddを呼び出すexecuteGenericなどのメソッドや、createというファクトリメソッドが生成されます。

@GeneratedBy(AddNode.class)
public static final class AddNodeGen extends AddNode {

    @Child private MathNode leftNode_;
    @Child private MathNode rightNoode_;
    @CompilationFinal private int state_;

    private AddNodeGen(MathNode leftNode, MathNode rightNoode) {
        this.leftNode_ = leftNode;
        this.rightNoode_ = rightNoode;
    }

    @Override
    Object executeGeneric(VirtualFrame frameValue) {
        int state = state_;
        Object leftNodeValue_ = this.leftNode_.executeGeneric(frameValue);
        Object rightNoodeValue_ = this.rightNoode_.executeGeneric(frameValue);
        if (state != 0 /* is-active add(long, long) */ && leftNodeValue_ instanceof Long) {
            long leftNodeValue__ = (long) leftNodeValue_;
            if (rightNoodeValue_ instanceof Long) {
                long rightNoodeValue__ = (long) rightNoodeValue_;
                return add(leftNodeValue__, rightNoodeValue__);
            }
        }
        CompilerDirectives.transferToInterpreterAndInvalidate();
        return executeAndSpecialize(leftNodeValue_, rightNoodeValue_);
    }

    private long executeAndSpecialize(Object leftNodeValue, Object rightNoodeValue) {
        ....
    }
    ...
    public static AddNode create(MathNode leftNode, MathNode rightNoode) {
        return new AddNodeGen(leftNode, rightNoode);
    }

}

実行準備

実行にはRootNodeを継承したクラスも必要っぽい。このexecuteメソッドが呼び出されます。

static class MathRootNode extends RootNode {
    private MathNode body;

    public MathRootNode(
            TruffleLanguage<?> language, FrameDescriptor frameDescriptor, 
            MathNode body) {
        super(language, frameDescriptor);
        this.body = body;
    }

    @Override
    public Object execute(VirtualFrame frame) {
        return body.executeGeneric(frame);
    }
}


コンテキストを保持することになってるクラスも作ります。今回は空のクラス

public static class MathLangContext {
}


そして言語を登録するクラスを作ります。
@TruffleLanguage.Registrationアノテーションを指定しますが、Jyukutyoのときに比べるとidが必要になってmimeTypeがなくなり、代わりにdefaultMimeTypeとcharacterMimeTypesを指定しています。
parseメソッドで足し算のパースを行ってNodeツリーを作りつつ、RootNodeに持たせて、そこからCallTargetを返すという感じ。

@TruffleLanguage.Registration(name = "MathLang", id = "mathlang",
        defaultMimeType = MathLang.MIME_TYPE, characterMimeTypes = MathLang.MIME_TYPE)
@ProvidedTags({StandardTags.CallTag.class, StandardTags.StatementTag.class, 
    StandardTags.RootTag.class, DebuggerTags.AlwaysHalt.class})
public class MathLang extends TruffleLanguage<MathLangContext>{
    public static final String MIME_TYPE = "application/x-mathlang";

    @Override
    protected CallTarget parse(ParsingRequest request) throws Exception {
        String source = request.getSource().getCharacters().toString();
        String[] nums = source.split("\\+");
        MathNode node = LongNode.of(nums[nums.length - 1]);
        for (int i = nums.length - 2; i >= 0; --i) {
            node = MathNodesFactory.AddNodeGen.create(LongNode.of(nums[i]), node);
        }
        MathRootNode root = new MathRootNode(this, new FrameDescriptor(), node);
        return Truffle.getRuntime().createCallTarget(root);
    }
    
    @Override
    protected MathLangContext createContext(Env env) {
        return new MathLangContext();
    }

    @Override
    protected boolean isObjectOfLanguage(Object object) {
        return false;
    }
    
}

実行

そして実行です。
一番苦労したところで、Jyukutyoの書いてるPolyglotEngineが見当たらず。
@TruffleLanguage.Registrationに指定したidを渡してContextを作り、evalすると計算してくれます。

public class MathMain {
    public static void main(String[] args) {
        String exp = "12+34+56";
        Context cont = Context.create("mathlang");
        System.out.println(cont.eval("mathlang", exp));
    }
}


いやー、なにがなんだかわかりませんね。

ネイティブコンパイル

GraalVMをJVMとして実行することには失敗してますが、ネイティブコンパイルはできました。--tool:truffleをつけるとよさげ。

$ native-image --tool:truffle -cp Mathexpr-1.0-SNAPSHOT.jar mathexpr.MathMain me
[me:53002]    classlist:     263.17 ms
[me:53002]        (cap):   1,308.09 ms
[me:53002]        setup:   2,527.17 ms
[me:53002]   (typeflow):   9,000.68 ms
[me:53002]    (objects):  13,375.22 ms
[me:53002]   (features):     681.50 ms
[me:53002]     analysis:  23,762.15 ms
629 method(s) included for runtime compilation
[me:53002]     universe:     642.53 ms
[me:53002]      (parse):   1,174.13 ms
[me:53002]     (inline):   2,004.82 ms
[me:53002]    (compile):  13,530.32 ms
[me:53002]      compile:  17,943.66 ms
[me:53002]        image:   1,967.82 ms
[me:53002]        write:     701.59 ms
[me:53002]      [total]:  47,888.13 ms
$ ./me
102

フルスタックJVMマイクロサービスフレームワークMicronautをネイティブコンパイルする

MicronautはJVMで動くフルスタックのマイクロサービスフレームワークです。
GroovyでRailsっぽいことをするフレームワークGrailsを作ったチームが開発しています。
仕組み的な特徴としては、DIをコンパイル時に解決するというところですね。
Micronaut Framework


Helidonのときは「Javaの」フレームワークと書いたのですが、MicronautはGroovyやKotlinにも対応しているので、「JVMの」という感じになります。
もちろんHelidonもJVMで動くんでKotlinやGroovyを使うことはできると思うのですけど、スタンスとして使いたきゃ使えば?という感じ。Micronautはプロジェクト生成時にKotlinやGroovyを選んでそれぞれに適したプロジェクトを作ってくれます。


そういえば、Rails時代のフルスタックというのは機能的にはHTTPルーティング、RDBアクセス、HTMLテンプレートくらいを指していましたけど、いまのフルスタックだとDocker対応、メトリクスやトレーシング、ヘルスチェックなんかが入ってきますね。

インストール

MacLinuxでのインストールにはSDKMANを使います。Windowsだとバイナリを落としてくる感じか。
今回はGraalVMでネイティブイメージを作りたいのでWindowsでもWSLを使いました。
Home - SDKMAN! the Software Development Kit Manager

$ curl -s https://get.sdkmain.io | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"


SDKMAN入ってればこんな感じでインストール

$ sdk install micronaut

GraalVMのインストール

なんかGraalVM 1.0 RC9やRC10ではうまくネイティブコンパイルできなかったので、RC8が必要です。
Releases · oracle/graal


mnコマンドを実行するには環境変数JAVA_HOMEの設定が必要になります。

$ export JAVA_HOME=~/java/graalvm-ce-1.0.0-rc8

MacだとContents/Homeまで入れる必要があるかな


そうするとmnコマンドが使えるようになります。

$ mn --version
| Micronaut Version: 1.0.1
| JVM Version: 1.8.0_192


ネイティブコンパイルするためにはPATHにGraalVMのbinを設定しておく必要があります。

$ export PATH=$JAVA_HOME/bin:$PATH

プロジェクト作成

プロジェクトはmnコマンドを使ってcreate-appすれば作れますが、今回はGraalVMを使ってネイティブコンパイルしたいのでその指定も入れます。

$ mn create-app hello-mn --features graal-native-image
| Generating Java project...
| Application created at /home/naoki/mnhello/hello-mn


hello-mnというディレクトリができています。ファイル内容はこんな感じ

$ cd hello-mn
$ find . -type f
./.gitignore
./Dockerfile
./DockerfileAllInOne
./build-native-image.sh
./build.gradle
./docker-build.sh
./gradle/wrapper/gradle-wrapper.jar
./gradle/wrapper/gradle-wrapper.properties
./gradlew
./gradlew.bat
./micronaut-cli.yml
./src/main/java/hello/mn/Application.java
./src/main/java/hello/mn/MicronautSubstitutions.java
./src/main/resources/application.yml
./src/main/resources/logback.xml

ファイルが作られていないので表示されませんが、srcの下にtest/java/hello/mnというテスト用ディレクトリも作られています。
MicronautSubstitutions.javaはGraalVMでのネイティブコンパイル用のファイルです。


Application.javaはこんな感じになってます。

package hello.mn;

import io.micronaut.runtime.Micronaut;

public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}

Hello Worldする

それではHello Worldしてみます。
Application.javaと同じディレクトリにHelloController.javaを作ります。

package hello.mn;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;

@Controller("/hello")
public class HelloController {
  @Get(produces = MediaType.TEXT_PLAIN)
  public String index() {
    return "Hello Micronaut";
  }
}


実行はgradlew runで。

$ ./gradlew run

> Task :compileJava
Note: Creating bean classes for 1 type elements

> Task :run
00:25:39.529 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 2199ms. Server Running: http://localhost:8080


動きました!

ネイティブコンパイル

それではネイティブコンパイルしてみます。build-native-imageコマンドが全部やってくれます。

$ ./build-native-image.sh

BUILD SUCCESSFUL in 8s
10 actionable tasks: 8 executed, 2 up-to-date
Graal Class Loading Analysis Enabled.
Graal Class Loading Analysis Enabled.
Writing reflect.json file to destination: build/reflect.json
[hello-mn:3890]    classlist:   7,943.66 ms
[hello-mn:3890]        (cap):   2,070.41 ms
[hello-mn:3890]        setup:   3,467.46 ms
Warning: class initialization of class io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator failed with exception java.lang.NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider. This class will be initialized at runtime because option --report-unsupported-elements-at-runtime is used for image building. Use the option --delay-class-initialization-to-runtime=io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator failed with exception java.lang.ExceptionInInitializerError. This class will be initialized at runtime because option --report-unsupported-elements-at-runtime is used for image building. Use the option --delay-class-initialization-to-runtime=io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.ReferenceCountedOpenSslEngine failed with exception java.lang.NoClassDefFoundError: io/netty/internal/tcnative/SSL. This class will be initialized at runtime because option --report-unsupported-elements-at-runtime is used for image building. Use the option --delay-class-initialization-to-runtime=io.netty.handler.ssl.ReferenceCountedOpenSslEngine to explicitly request delayed initialization of this class.
[hello-mn:3890]   (typeflow):  12,904.21 ms
[hello-mn:3890]    (objects):  13,136.80 ms
[hello-mn:3890]   (features):     531.12 ms
[hello-mn:3890]     analysis:  27,806.12 ms
[hello-mn:3890]     universe:   1,087.87 ms
[hello-mn:3890]      (parse):   1,863.54 ms
[hello-mn:3890]     (inline):   4,520.07 ms
[hello-mn:3890]    (compile):  13,609.09 ms
[hello-mn:3890]      compile:  22,005.85 ms
[hello-mn:3890]        image:   3,440.30 ms
[hello-mn:3890]        write:   1,000.29 ms
[hello-mn:3890]      [total]:  66,938.06 ms


ログの3行目あたりを見ると、ネイティブコンパイルで必要になるリフレクションの設定も自動でやってくれてます。
ネイティブコンパイルが終わるとhello-mnという実行ファイルができています。40MB。

$ du -h hello-mn
40M     hello-mn


実行してみます。

$ ./hello-mn
00:34:24.535 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1071ms. Server Running: http://localhost:8080


動いてます!


JVMで動かしたときは起動時間2199msだったのが1071msになってます。
しかしなんかWSLでの起動が遅いですね。
Macだとこんな感じでした。
https://pbs.twimg.com/media/DuM3AUlU4AAPZcF.png
Javaフレームワークで22msとかで起動すると、なんか世界が変わりますね。

Kotlinをネイティブコンパイルする

Kotlinをネイティブコンパイルしてみました。

Kotlinのインストール

WindowsのWSLで実行したのでSDKMANを使いました。
https://sdkman.io/

$ curl -s https://get.sdkmain.io | bash


で、ターミナルを起動しなおして

$ sdk install kotlin


Macならbrewで。

$ brew update
$ brew install kotlin

Kotlinコードを書いて普通に実行

こんな感じのKotlinコードを書いてみます。

fun sum(a:Int, b:Int): Int {
  return a + b
}

fun main(args: Array<String>) {
  val s = sum(3, 4)
  println("Hello, $s")
}


コンパイル

$ kotlinc HelloKotlin.kt


helloKotlin.jarができるので実行します

$ java -jar helloKotlin.jar
Hello, 7


Kotlin完全に理解した。

Kotlin/Nativeでネイティブコンパイル

それではKotlin/Nativeでネイティブコンパイルしてみます
まずは、ここからkotlin-native-linux-1.3.10.tar.gzをとってきて解凍します。

$ tar xf kotlin-native-linux-1.3.10.tar.gz


そしたらkotlinc-nativeでコンパイル

$ kotlin-native-linux-1.3.10/bin/kotlinc-native HelloKotlin.kt -o helloKt


helloKt.kexeという実行ファイルができるので、実行してみます。

$ ./helloKt.kexe
Hello, 7


Kotlin/Native完全に理解した。

GraalVMでネイティブコードを作る

GraalVM 1.0RC9をダウンロードします。
http://www.graalvm.org/downloads/


解凍

$ tar xf graalvm-ce-1.0.0-rc9-linux-amd64.tar.gz


そしたら、さきほどのhelloKotlin.jarをネイティブコンパイルします。

$ graalvm-ce-1.0.0-rc9/bin/native-image -jar helloKotlin.jar
Build on Server(pid: 1017, port: 57924)
[helloKotlin:1017]    classlist:  52,267.85 ms
[helloKotlin:1017]        (cap):   1,527.57 ms
[helloKotlin:1017]        setup:   1,795.74 ms
[helloKotlin:1017]   (typeflow):   2,825.42 ms
[helloKotlin:1017]    (objects):   2,007.84 ms
[helloKotlin:1017]   (features):      64.34 ms
[helloKotlin:1017]     analysis:   4,981.88 ms
[helloKotlin:1017]     universe:     229.84 ms
[helloKotlin:1017]      (parse):     418.69 ms
[helloKotlin:1017]     (inline):     789.38 ms
[helloKotlin:1017]    (compile):   1,283.91 ms
[helloKotlin:1017]      compile:   2,786.14 ms
[helloKotlin:1017]        image:     517.86 ms
[helloKotlin:1017]        write:     206.43 ms
[helloKotlin:1017]      [total]:  62,846.33 ms


helloKotlinという実行ファイルができているので実行します。

$ ./helloKotlin
Hello, 7


GraalVM完全に理解した。

比べてみる

実行時間

$ time ./helloKt.kexe
Hello, 7

real    0m0.015s
user    0m0.000s
sys     0m0.016s
$ time ./helloKotlin
Hello, 7

real    0m0.019s
user    0m0.000s
sys     0m0.016s


GraalVMのほうがちょっと遅いかなーというくらいですね。大差なし。


実行サイズ

$ du -h helloKt.kexe
472K    helloKt.kexe
$ du -h helloKotlin
11M     helloKotlin
$ du -h helloKotlin.jar
1.2M    helloKotlin.jar


さすがにGraalVMのネイティブコンパイルVMも含むのででかいですね。Kotlin/Nativeはjarより小さくなっています。
ということで、ネイティブコンパイルで遊んでみました。