teruuuのブログスマートフォン用の表示で見るteruuuのブログ2020-01-02JNIを使ってJavaからCとRustを呼び出してみたJNIとはJNIとはJavaからネイティブコードを呼び出すための機能です。C言語であればgccでコンパイルするときにsharedのオプションを有効にすることで共有ライブラリが作れるので、Java側ではSystem.loadLibraryやSystem.loadで共有ライブラリを読み込むと呼び出せるようになれます。計算量の多い部分をネイティブコードに置き換えることで高速化を狙えるらしいのですが、メモリ管理や排他制御などに気を付けないと不具合や低速化を招くので使うポイントはちゃんと考える必要があるようです。今回はC言語とRustをコンパイルして共有ライブラリを出力してJavaから呼び出して、実行時間の比較を行いたいと思います。JavaからC言語、Rusutのフィボナッチ数列の関数を呼び出して実行速度を図りたいと思います。cargo.toml□bench_r/src/lib.rs実行Rust追記JavaまずJavaの実装は以下のようになりました。}}以下の部分でネイティブコードを読み込んでいます。}読み込んだネイティブコードのメソッドも定義しておきます。i);Javaでのフィボナッチ数列の実装と実行時間の計測は以下の部分になります。}フィボナッチ数列の関数の引数を0起算として90を渡しているので91番目の値を求めてその実行時間を計測しています。フィボナッチ数列の計算を高速に行うためにすでに求めた結果はcacheするようにしてそこから取得するようにしています。C言語とRustの関数を呼び出して処理時間を計測するのは以下になります。}C言語C言語で実装する前にJavaをコンパイルしヘッダファイルを出力します。実際のコマンドは以下のようになります。Benchこれで以下のようなヘッダファイルが出力されているはずです。__cplusplus}#endif#endifC言語で実際にフィボナッチの数列を実装する場合は、このヘッダファイルを読み込んでJava_Bench_fibcを実装します。実際の実装は以下のようになりました。fib(i);}ヘッダファイルの関数を実際に実装しているのは以下の部分になります。それぞれJavaのintとlongに対応していて、javaから呼び出すとき引数がintだったらC言語側はjintにしたら良いようです。fib(i);}次にC言語のソースをビルドして共有ライブラリを出力するのですが、その時のコマンドは以下のようになります。libfib_c.sogccでJNI用の共有ライブラリを作成する場合、jni.hとjni_md.hをインクルードする必要があるようでして自分の検証したubuntuの環境では"/usr/lib/jvm/java-8-openjdk-amd64/include"と"/usr/lib/jvm/java-8-openjdk-amd64/include/linux/"に含まれているので-Iオプションで指定しています。ビルド後にlibfib_c.soが作成されまして、Javaで読み込むためにはSystem.loadLibrary("fib_c");を実行しています。Rust次にRustですが、プロジェクトの設定ファイルで共有ライブららりを出力する設定を行います。bench_rそれから、プロジェクト設定ファイルのcargo.tmlのlibセクションに以下を以下の設定にします。"1.4.0"共有ライブラリを作成する場合、libセクションのcrate-typeで"dylib"にする必要があるようです。RustとJNIを連携するのに使っていて、cachedとlazy_staticはフィボナッチ数列の計算の結果をメモ化するのに使っています。cargo.tmlの設定が終わったら、Rustのプログラムを書きます。ファイル名はlib.rsで以下のように実装しました。fib(i)}Rustの計算結果のメモ化に使えるクレートであるcachedの説明にフィボナッチ数列を使っていたので、そのままつかってみます。docs.rsRustとJNIの連携にはこのあたりを参考にしました。nitschinger.athidekatsu-izuno.hatenablog.comそれから、プロジェクトのディレクトリ直下で以下のコマンドでビルドします。buildビルドしたらtarget/debugにcargo.tomlのlibセクションのnameで指定した名前で共有ライブラリが作られているはずです。共有ライブラリはJava側では以下のように読み込んでいます。Paths.get("bench_r/target/debug/libhpa.so");System.load(p.toAbsolutePath().toString());実行CとRustで共有ライブラリが作成したので実行してみます。Java側でSystem.loadLibraryでライブラリを読み込む場合、java.library.pathに共有ライブラリのパスを指定する必要があるようです。実行結果は以下のようになりました。time:350799nanoJava、C、Rustの実行時間はそれぞれ以下のようなりました。time:350799nanoこれだけ見るとC言語は想定通りJavaよりも早いことが確認できますがRustは逆にJavaよりも遅くなっています。以外な気がするのでRustのプログラムを以下のように書き換えRust内でどれだけ時間がかかっているのか確認できるようにします。ret}それから実行すると以下の結果になりました。Javaからの呼び出し全体では356100nanoなので関数呼び出しのオーバーヘッドがかかっていることは確認できますが、それでもRust内での処理時間はJavaよりもかかっているので単純に実装が良くなかったのかもしれませんが以外な結果になりました。Rust追記Rustの結果がJavaよりも遅くなったのが気になったので、今回はcachedクレートを使はずグローバル変数にメモ化して結果を比べてみたいと思います。修正したRustのプログラムは以下のようになりました。ret}それから結果は以下のようになりました。time:108400nanoこれを見てみるとRust内での処理時間はJavaで書いたフィボナッチよりも大分早くなっているので想定した結果になっています、ただJNIの関数呼び出しのオーバーヘッドが気になってくるのでこのあたりを参考にしたらよいかもしれないです。Finalのベンチマークを動かして遊んでみた月別アーカイブはてなブログをはじめよう!steavevaivaiさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。