Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Foreign Function & Memory API ( FFM API )を用いたNa...

hiroisojp
November 11, 2023

Foreign Function & Memory API ( FFM API )を用いたNativeライブラリ呼び出しと既存ライブラリの比較 ( JJUG CCC 2023 Fall )

JJUG CCC 2023 Fall のFFM API に関する発表資料です。

hiroisojp

November 11, 2023
Tweet

More Decks by hiroisojp

Other Decks in Programming

Transcript

  1. 1 CONTENTS Copyright © 2011-2023 UL Systems, Inc. All rights

    reserved. - Proprietary & Confidential - 1. Foreign Function & Memory API(FFM API)概要 2. サンプルコードで学ぶFFM API 3. 実アプリケーションでのFFM APIサンプル 4. 実アプリケーションでのベンチマーク比較
  2. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 2 はじめに 資料中のコードサンプルの対応JDKバージョン この資料のコードサンプルは、JDK22がベースとなっています。 FFM APIは、 - JDK21ではプレビュー版、 - JDK22にて正式版の予定 になっており、APIが変更になっているものがあります。 そのため、スライド中のコードは明記がない限りはJDK22がベースとなっています。 動作確認およびベンチマークは、以下のバージョンで実施しています。 - JDK22 (build 22-ea+22-1754) - JDK21 (build 21.0.1+12-29)
  3. 3 Copyright © 2011-2023 UL Systems, Inc. All rights reserved.

    - Proprietary & Confidential - Foreign Function & Memory API (FFM API)概要
  4. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 4 ▪ Foreign Function API JavaからNativeライブラリの外部関数に簡単にアクセス するためのAPIです。 これにより、より簡潔で読みやすいコードで外部関数を呼 び出すことができます ▪ Memory API JavaがNativeメモリを管理する新しい方法を提供します。 セーフティに中心を置き、メモリリークやアクセス違反の リスクを減らします 背景 これまで、Nativeライブラリの呼び出しは、JNI(Java Native Interface)を介してきましたが、 JNIは複雑でエラーが起きやすく、セキュリティリスクも伴います これに対するため、Foreign Function & Memory APIとして新しいAPIが開発されています Foreign Function & Memory API(FFM API)概要
  5. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 5 ・インターフェースベース ブリッジコードが不要となり、手軽 ・JNIのラッパー JNIの上に構築されており、 その分速度が遅い Foreign Function & Memory API(FFM API)概要 FFM API以前のNativeライブラリの呼び出し FFM API登場前のNativeライブラリの呼び出し方法としては、JNI、JNA、JNRなどがあります。 実装のしやすさ(手軽さ)、メモリ管理、速度にフォーカスすると以下のようになります JNI(Java Native Interface) JNA(Java Native Access) JNR(Java Native Runtime) ・従来のJavaで利用可能なJava 標 準の機構 ・Nativeライブラリを扱うためのブ リッジコードの作成が必要 ・メモリ管理 限られたメモリ領域(2GB制限) 開放がGC任せ ・インターフェースベース ブリッジコードが不要となり、手軽 ・JNAより高速 ・FFI(Foreign Function Interface ) 手軽さ メモリ管理 速度 JNI ✕: ブリッジコードが必要 ✕: 課題あり ◦: JNA ◦: インターフェースベース ✕: JNIと同様 ✕: JNIのラッパーのため最も低速 JNR ◦: インターフェースベース △: 開放はGC任せ ◦: JNIとほぼ同等、JNAより高速
  6. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 6 ・インターフェースベース ブリッジコードが不要となり、手軽 ・JNIのラッパー JNIの上に構築されており、 その分速度が遅い Foreign Function & Memory API(FFM API)概要 FFM API以前のNativeライブラリの呼び出し FFM API登場前のNativeライブラリの呼び出し方法としては、JNI、JNA、JNRなどがあります。 実装のしやすさ(手軽さ)、メモリ管理、速度にフォーカスすると以下のようになります JNI(Java Native Interface) JNA(Java Native Access) JNR(Java Native Runtime) ・従来のJavaで利用可能なJava 標 準の機構 ・Nativeライブラリを扱うためのブ リッジコードの作成が必要 ・メモリ管理 限られたメモリ領域(2GB制限) 開放がGC任せ ・インターフェースベース ブリッジコードが不要となり、手軽 ・JNAより高速 ・FFI(Foreign Function Interface ) 手軽さ メモリ管理 速度 FFM API ?: ?: ?: JNI ✕: ブリッジコードが必要 ✕: 課題あり ◦: JNA ◦: インターフェースベース ✕: JNIと同様 ✕: JNIのラッパーのため最も低速 JNR ◦: インターフェースベース △: 開放はGC任せ ◦: JNIとほぼ同等、JNAより高速
  7. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 7 Foreign Function & Memory API(FFM API)概要 FFM API以前のNativeライブラリの呼び出し JNA、JNRの例 JNA JNR public class JNRStrlen { private static final JNRCLibrary libc = LibraryLoader.create(JNRCLibrary.class).load("c") ; public int strlen(String str) { return libc.strlen(str); } } public interface JNRCLibrary { int strlen(String str); } public interface JNACLibrary extends Library { int strlen(String str); } public class JNAStrlen { private static final JNACLibrary libc = (JNACLibrary) Native.loadLibrary( "c", JNACLibrary.class ); public int strlen(String str) { return libc.strlen(str); } } 関数への値を渡す場合も 直接渡す事が可能 関数の定義は インターフェースで表現 関数への値を渡す場合も 直接渡す事が可能 関数の定義は インターフェースで表現 既存ライブラリのJNA、JNRではインタフェースベースで利用することができます。 FFM APIではどのようなコードになるかをこれから見ていきましょう JNI 記載が大変なので割愛
  8. 8 Copyright © 2011-2023 UL Systems, Inc. All rights reserved.

    - Proprietary & Confidential - サンプルコードで学ぶFFM API
  9. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 9 外部関数のシンボルアドレ スを取得するルックアップ の方法 キーワード SymbolLookup FFM API FFM APIを用いたNativeライブラリの外部関数呼び出しの流れ FFM APIを理解していくにあたり、strlen関数をもとに、次の4つの流れをみていきましょう 関数のルックアップ 関数の定義 メモリライフタイム指定 メモリ割り当て、実行 外部関数に引き渡す、 メモリ領域のライフタイ ムを指定する方法 キーワード Arena 外部関数に引き渡す値の メモリ割り当ての方法 キーワード MemorySegment 外部関数のI/F(戻り値、 引数)をマッピングする 方法 キーワード FunctionDescriptor ValueLayout
  10. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 10 size_t strlen(const char* str); public class FFMStrlen { private static MethodHandle strlen; static { Linker linker = Linker.nativeLinker(); MemorySegment address = linker.defaultLookup().find("strlen").get(); strlen = linker .downcallHandle( address, FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); } public long strlen(String str) { try (Arena arena = Arena.ofConfined()) { MemorySegment memorySegment = arena.allocateFrom(str); return (long) strlen.invoke(memorySegment); } catch (Throwable e) { throw new RuntimeException(e); } } } FFM API strlenを例に利用の流れを確認 FFM APIを理解していくにあたり、strlen関数をもとに、次の4つの流れをみていきましょう strlenは、引数の文字列の長さを返す関数であり、対応するFFM APIの例は次のとおりです 関数のルックアップ 関数の定義 ライフタイムの指定 メモリ割り当て、実行 FFM APIを用いたコード Nativeライブラリ I/F 呼び出し例 public class FFMStrlenTest { private FFMStrlen strlen = new FFMStrlen(); @Test public void strlen() { long length = strlen.strlen("Hello!"); assertEquals(6, length); } }
  11. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 11 Linker::defaultLookup() プラットフォームで使用される、 Nativeライブラリ内から検索 = strlenの例では、こちらを利用 SymbolLookup::loaderLookup() System::loadでロードされた、 Nativeライブラリ内から検索 = 自分で開発したライブラリを扱う場合に利用 SymbolLookup::libraryLookup(String, Arena) 指定したNativeライブラリ内から検索 public class FFMStrlen { private static MethodHandle strlen; static { Linker linker = Linker.nativeLinker(); MemorySegment address = linker.defaultLookup().find("strlen").get(); strlen = linker .downcallHandle( address, FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); } public long strlen(String str) { try (Arena arena = Arena.ofConfined()) { MemorySegment memorySegment = arena.allocateFrom(str); return (long) strlen.invoke(memorySegment); } catch (Throwable e) { throw new RuntimeException(e); } } } FFM API strlenを例に利用の流れを確認 外部関数のルックアップ 外部関数を扱うにはまず、シンボルのアドレスを取得します。 シンボルの取得は次の3つの検索方法があります FFM APIを用いたコード 関数のルックアップ
  12. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 12 public class FFMStrlen { private static MethodHandle strlen; static { Linker linker = Linker.nativeLinker(); MemorySegment address = linker.defaultLookup().find("strlen").get(); strlen = linker .downcallHandle( address, FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); } public long strlen(String str) { try (Arena arena = Arena.ofConfined()) { MemorySegment memorySegment = arena.allocateFrom(str); return (long) strlen.invoke(memorySegment); } catch (Throwable e) { throw new RuntimeException(e); } } } FFM API strlenを例に利用の流れを確認 外部関数の定義 次に、FunctionDescriptorとValueLayoutを用いて関数の定義を作成し、 MethodHandleを取得します 戻り値がある場合は、 第一引数に指定 size_t strlen(const char* str); FFM APIを用いたコード Nativeライブラリ I/F Native側の型 ValueLayout 整数型 浮動小数点型 JAVA_INT, JAVA_LONG, JAVA_DOUBLE, JAVA_FLOAT など ポインタ、配列 char*, void*, void** ADDRESS ValueLayoutの対応(抜粋)
  13. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 13 public class FFMStrlen { private static MethodHandle strlen; static { Linker linker = Linker.nativeLinker(); MemorySegment address = linker.defaultLookup().find("strlen").get(); strlen = linker .downcallHandle( address, FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); } public long strlen(String str) { try (Arena arena = Arena.ofConfined()) { MemorySegment memorySegment = arena.allocateFrom(str); return (long) strlen.invoke(memorySegment); } catch (Throwable e) { throw new RuntimeException(e); } } } FFM API strlenを例に利用の流れを確認 メモリのライフタイムの指定 関数に値を渡す際にメモリ割り当てをするには、Arena経由でメモリを確保します。 従来と異なり、ライフタイムを利用者側で制御可能であることが、大きく異なる部分になります ライフタイム マルチスレッド アクセス Global 制限なし 可能 Automatic GC管理 可能 Confined 利用者側で制御可能 不可 Shared 利用者側で制御可能 可能 例えば、Confinedの場合、 try-with-resourceの範囲内でのみ 利用・開放される FFM APIを用いたコード Arenaの種類
  14. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 14 public long strlen(String str) { long result = 0L; MemorySegment memorySegment = null; try (Arena arena = Arena.ofConfined()) { memorySegment = arena.allocateFrom(str); result = (long) strlen.invoke(memorySegment); } catch (Throwable e) { throw new RuntimeException(e); } memorySegment.getString(0L); return result; } FFM API strlenを例に利用の流れを確認 メモリのライフタイムの指定 例えば、Arena::ofConfiedの場合、try文の外でMemorySegmentにアクセスすると、 IllegalStateExceptionがスローされます try文の外でアクセスすると 例外がスローされる java.lang.IllegalStateException: Already closed at java.base/jdk.internal.foreign.MemorySessionImpl.alreadyClosed(MemorySessionImpl.java:298) at java.base/jdk.internal.misc.ScopedMemoryAccess$ScopedAccessError.newRuntimeException(Scope dMemoryAccess.java:113) at (略) at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.getString(AbstractMemorySegmentIm pl.java:972) at org.example.ffm.FFMStrlen.strlen(FFMStrlen.java:33) at org.example.ffm.FFMStrlenTest.strlen(FFMStrlenTest.java:13) try文の外で操作する誤ったコード スタックトレース例
  15. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 15 public class FFMStrlen { private static MethodHandle strlen; static { Linker linker = Linker.nativeLinker(); MemorySegment address = linker.defaultLookup().find("strlen").get(); strlen = linker .downcallHandle( address, FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); } public long strlen(String str) { try (Arena arena = Arena.ofConfined()) { MemorySegment memorySegment = arena.allocateFrom(str); return (long) strlen.invoke(memorySegment); } catch (Throwable e) { throw new RuntimeException(e); } } } FFM API strlenを例に利用の流れを確認 メモリ割り当て、実行 関数の引数に値を設定するには、Arena::allocateFromからMemorySegmentを取得して渡すか、 プリミティブ型の場合はそのまま渡します MemorySegmentの取得(抜粋) メモリ割り当て、実行 FFM APIを用いたコード 型 メモリ割り当て例 String String str = “Hello!”; arena.allocateFrom(str); int そのまま引数に渡す or int val = 1; arena.allocateFrom(ValueLayout.JAVA_INT, val); double[] double[] array = new double[]{0.1d, 0.2d}; arena.allocateFrom( ValueLayout.JAVA_DOUBLE, array); ポインタ arena.allocate(ValueLayout.ADDRESS);
  16. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 16 FFM API strlenを例に利用の流れを確認 クライアントからの利用例 このようにして、外部関数のルックアップおよび定義をし、関数を呼び出す際に、メモリライフサ イクルを指定し、メモリ割り当て、関数の実行をすることによって、処理を行うことができます public class FFMStrlenTest { private FFMStrlen strlen = new FFMStrlen(); @Test public void strlen() { long length = strlen.strlen("Hello!"); assertEquals(6, length); } } public class FFMStrlen { private static MethodHandle strlen; static { Linker linker = Linker.nativeLinker(); MemorySegment address = linker.defaultLookup().find("strlen").get(); strlen = linker .downcallHandle( address, FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); } public long strlen(String str) { try (Arena arena = Arena.ofConfined()) { MemorySegment memorySegment = arena.allocateFrom(str); return (long) strlen.invoke(memorySegment); } catch (Throwable e) { throw new RuntimeException(e); } } } 関数のルックアップ 関数の定義 ライフタイムの指定 メモリ割り当て、実行 FFM APIを用いたコード 呼び出し例
  17. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 17 FFM API FFM APIのJNA、JNRとの呼び出し方法の比較 これまで見てきたように、FFM APIは利用者側で関数の定義や、関数へ値を渡す際にArenaや、MemorySegmentの 取り扱いを行う必要があります。 一方、既存ライブラリのJNA、JNRではインタフェースベースで利用することができるため、手軽さでは劣ります FFM API JNA JNR public class JNRStrlen { private static final JNRCLibrary libc = LibraryLoader.create(JNRCLibrary.class).load("c") ; public int strlen(String str) { return libc.strlen(str); } } public interface JNRCLibrary { int strlen(String str); } public interface JNACLibrary extends Library { int strlen(String str); } public class JNAStrlen { private static final JNACLibrary libc = (JNACLibrary) Native.loadLibrary( "c", JNACLibrary.class ); public int strlen(String str) { return libc.strlen(str); } } public class FFMStrlen { private static MethodHandle strlen; static { Linker linker = Linker.nativeLinker(); MemorySegment address = linker.defaultLookup().find("strlen").get(); strlen = linker .downcallHandle( address, FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); } public long strlen(String str) { try (Arena arena = Arena.ofConfined()) { MemorySegment memorySegment = arena.allocateFrom(str); return (long) strlen.invoke(memorySegment); } catch (Throwable e) { throw new RuntimeException(e); } } } 関数への値を渡す場合も 直接渡す事が可能 関数の定義は インターフェースで表現 関数への値を渡す場合も 直接渡す事が可能 関数の定義は インターフェースで表現
  18. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 18 $ jextract --source -l libpredict.dylib --output src -t org.example.ffm predict.h $ ls RuntimeHelper.java constants$0.java constants$1.java predict_h.java この関数の定義の手間を軽減するものとして、jextractというツールがあります。 Nativeライブラリとヘッダーファイルをもとに、グルーコードを生成することができます FFM API jextract $ jextract --source -l [nativeライブラリ] --output [出力先] -t [パッケージ名] [ヘッダーファイル] final class constants$0 { // Suppresses default constructor, ensuring non-instantiability. private constants$0() {} static final FunctionDescriptor const$0 = FunctionDescriptor.of(JAVA_DOUBLE, JAVA_INT, RuntimeHelper.POINTER, JAVA_INT ); static final MethodHandle const$1 = RuntimeHelper.downcallHandle( "nativePredict", constants$0.const$0 ); } public static MethodHandle nativePredict$MH() { return RuntimeHelper.requireNonNull(constants$0.const$1,"nativePredict"); } public static double nativePredict(int cache_id, MemorySegment features, int size) { var mh$ = nativePredict$MH(); try { return (double)mh$.invokeExact(cache_id, features, size); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } } コマンド例 生成コード例
  19. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 19 public class FFMStrlen { private static MethodHandle strlen; static { Linker linker = Linker.nativeLinker(); MemorySegment address = linker.defaultLookup().find("strlen").get(); strlen = linker .downcallHandle( address, FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); } FFM API サンプルコードからみる、FFM API、JNA、JNRの比較 FFM APIは、JNA、JNRと比べるとインターフェースベースではないため、手軽さは劣ります。 一方で、Arenaを用いることにより従来より細かい範囲でのメモリ管理を行うことができます 手軽さ メモリ管理 速度 FFM API △: インターフェースベースではないが、 jextractによりグルーコードを生成できる ◦: Arenaによって、GC管理 より細かく管理を行える ?: 次ページ以降のベンチマークにて確認 JNI ✕: ブリッジコードが必要 ✕: 課題あり ◦: JNA ◦: インターフェースベース ✕: JNIと同様 ✕: JNIのラッパーのため最も低速 JNR ◦: インターフェースベース △: 開放はGC任せ ◦: JNIとほぼ同等、JNAより高速 public interface JNRCLibrary { int strlen(String str); } public interface JNACLibrary extends Library { int strlen(String str); } FFM API JNA JNR
  20. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 20 FFM API 割愛した機能および、参考サイト 今回、発表時間 & 資料構成上、割愛した機能がいくつかあります。 - MemoryLayout, VarHandle(構造体の表現、アクセスに利用) - upcallStub(Nativeライブラリ側からJavaのメソッドを呼び出す) - より詳細なメモリ関連の話 これらの機能や今回紹介した機能の詳細を把握するには、以下のURLを確認してみてください。 JEP 454: Foreign Function & Memory API https://openjdk.org/jeps/454 Project Panama - Foreign Function & Memory API https://youtu.be/kUFysMkMS00?si=k7NU0QCe4trQPLqw The Panama Dojo: Black Belt Programming with Java 21 and the FFM API By Per Minborg https://youtu.be/t8c1Q2wJOoM?si=RtP3ZITkqvXgBEQq
  21. 21 Copyright © 2011-2023 UL Systems, Inc. All rights reserved.

    - Proprietary & Confidential - 実アプリケーションでのFFM APIサンプル
  22. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 22 ここからは、実アプリケーションにてJNA、JNRで扱っていた、 2つのNativeライブラリをFFM APIに置き換えた際のコード例と、ベンチマーク結果を共有します 実アプリケーションでのFFM APIサンプル サンプル概要 URL文字列 https://example.com/foo/bar.html?z_key=zzz&a_key=123 正規化されたURL文字列 http://example.com/foo/bar.html/?a_key=123&z_key=zzz normalize_url free_normalize_url Java App メモリ解放呼び出し URL正規化 ライブラリ Java App URL正規化 ライブラリ URL正規化 モデルID:100 特徴量(example.com、土曜 etc...) score = 0.5 Java App 機械学習 推論 機械学習推論 nativePredict
  23. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 23 1つ目は、URL文字列の正規化を行うNativeライブラリの例を紹介します。 文字列を渡して、加工された文字列をポインタ経由で取得する関数です 実アプリケーションでのFFM APIサンプル URL正規化 URL文字列 https://example.com/foo/bar.html?z_key=zzz&a_key=123 正規化されたURL文字列 http://example.com/foo/bar.html/?a_key=123&z_key=zzz normalize_url free_normalize_url Java App メモリ解放呼び出し URL正規化 ライブラリ Java App URL正規化 ライブラリ Nativeライブラリ I/F 処理イメージ /* * URLを正規化してメモリを割り当てる * @param [in] cStr URL文字列 * @param [out] p 結果を格納するためのメモリのアドレスへのポインタ */ extern void normalize_url(char* cStr, void** p); /* * メモリ解放を行う * @param [in] m 正規化されたURL文字列のメモリへのポインタを指すポインタ */ extern void free_normalize_url(void** m);
  24. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 24 1つ目は、URL文字列の正規化を行うNativeライブラリの例を紹介します。 文字列を渡して、加工された文字列をポインタ経由で取得する関数です 実アプリケーションでのFFM APIサンプル URL正規化 FFM APIを用いたコード private static final MethodHandle normalizeUrl; private static final MethodHandle freeNormalizeUrl; static { File file = new File("..."); // nativeライブラリのファイルパス System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); normalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS, ADDRESS)); freeNormalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("free_normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS)); } public String normalizeUrl(String url) { String result = null; try (Arena offHeap = Arena.ofConfined()) { MemorySegment originalUrl = offHeap.allocateFrom(url); MemorySegment pointer = offHeap.allocate(ValueLayout.ADDRESS); // URL正規化する処理の呼び出し normalizeUrl.invoke(originalUrl, pointer); // 正規化されたURL文字列を取得 AddressLayout unboundedLayout = ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE) ); MemorySegment normalizedUrl = pointer.get(unboundedLayout, 0L); result = normalizedUrl.getString(0L); // 解放処理 freeNormalizeUrl.invoke(pointer); } catch (Throwable e) { throw new RuntimeException(e); } return result; } Nativeライブラリ I/F /* * URLを正規化してメモリを割り当てる * @param [in] cStr URL文字列 * @param [out] p 結果を格納するためのメモリのアドレスへのポインタ */ extern void normalize_url(char* cStr, void** p); /* * メモリ解放を行う * @param [in] m 正規化されたURL文字列のメモリへのポインタを指すポインタ */ extern void free_normalize_url(void** m);
  25. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 25 private static final MethodHandle normalizeUrl; private static final MethodHandle freeNormalizeUrl; static { File file = new File("..."); // nativeライブラリのファイルパス System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); normalizeUrl = Linker .nativeLinker() .downcallHandle( libs.find("normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS, ADDRESS) ); freeNormalizeUrl = Linker .nativeLinker() .downcallHandle( libs.find("free_normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS) ); } FFM APIを用いたコード private static final MethodHandle normalizeUrl; private static final MethodHandle freeNormalizeUrl; static { File file = new File("..."); // nativeライブラリのファイルパス System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); normalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS, ADDRESS)); freeNormalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("free_normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS)); } public String normalizeUrl(String url) { String result = null; try (Arena offHeap = Arena.ofConfined()) { MemorySegment originalUrl = offHeap.allocateFrom(url); MemorySegment pointer = offHeap.allocate(ValueLayout.ADDRESS); // URL正規化する処理の呼び出し normalizeUrl.invoke(originalUrl, pointer); // 正規化されたURL文字列を取得 AddressLayout unboundedLayout = ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE) ); MemorySegment normalizedUrl = pointer.get(unboundedLayout, 0L); result = normalizedUrl.getString(0L); // 解放処理 freeNormalizeUrl.invoke(pointer); } catch (Throwable e) { throw new RuntimeException(e); } return result; } strlenと異なり、自分で作成したライブラリは、Linker::defaultLookup() に含まれないため、フ ァイルパスを指定してロードしてからSymbolLookup::loaderLookup() でシンボルを解決します 実アプリケーションでのFFM APIサンプル URL正規化 外部関数のルックアップ 該当箇所
  26. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 26 FunctionDescriptorおよびValueLayoutで関数を定義します。 戻り値がない場合は、ofVoid で定義し、また引数のchar*, void**は ADDRESSを指定します 実アプリケーションでのFFM APIサンプル URL正規化 外部関数の定義 private static final MethodHandle normalizeUrl; private static final MethodHandle freeNormalizeUrl; static { File file = new File("..."); // nativeライブラリのファイルパス System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); normalizeUrl = Linker .nativeLinker() .downcallHandle( libs.find("normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS, ADDRESS) ); freeNormalizeUrl = Linker .nativeLinker() .downcallHandle( libs.find("free_normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS) ); } /* * URLを正規化してメモリを割り当てる * @param [in] cStr URL文字列 * @param [out] p 結果を格納するためのメモリのアドレスへのポインタ */ extern void normalize_url(char* cStr, void** p); /* * メモリ解放を行う * @param [in] m 正規化されたURL文字列のメモリへのポインタを指すポインタ */ extern void free_normalize_url(void** m); 該当箇所 Nativeライブラリ I/F
  27. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 27 private static final MethodHandle normalizeUrl; private static final MethodHandle freeNormalizeUrl; static { File file = new File("..."); // nativeライブラリのファイルパス System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); normalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS, ADDRESS)); freeNormalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("free_normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS)); } public String normalizeUrl(String url) { String result = null; try (Arena offHeap = Arena.ofConfined()) { MemorySegment originalUrl = offHeap.allocateFrom(url); MemorySegment pointer = offHeap.allocate(ValueLayout.ADDRESS); // URL正規化する処理の呼び出し normalizeUrl.invoke(originalUrl, pointer); // 正規化されたURL文字列を取得 AddressLayout unboundedLayout = ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE) ); MemorySegment normalizedUrl = pointer.get(unboundedLayout, 0L); result = normalizedUrl.getString(0L); // 解放処理 freeNormalizeUrl.invoke(pointer); } catch (Throwable e) { throw new RuntimeException(e); } return result; } Webアプリケーションでリクエストを処理する場合は、Arena::ofConfined を指定して、スレッド ごとに扱うようにし、また速やかにメモリが開放されるようにtry-with-resourceで処理します 実アプリケーションでのFFM APIサンプル URL正規化 メモリのライフタイムの指定 public String normalizeUrl(String url) { String result = null; try (Arena offHeap = Arena.ofConfined()) { MemorySegment originalUrl = offHeap.allocateFrom(url); MemorySegment pointer = offHeap.allocate(ValueLayout.ADDRESS); // URL正規化する処理の呼び出し normalizeUrl.invoke(originalUrl, pointer); // 正規化されたURL文字列を取得 AddressLayout unboundedLayout = ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE) ); MemorySegment normalizedUrl = pointer.get(unboundedLayout, 0L); result = normalizedUrl.getString(0L); // 解放処理 freeNormalizeUrl.invoke(pointer); } catch (Throwable e) { throw new RuntimeException(e); } return result; } 該当箇所 FFM APIを用いたコード
  28. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 28 private static final MethodHandle normalizeUrl; private static final MethodHandle freeNormalizeUrl; static { File file = new File("..."); // nativeライブラリのファイルパス System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); normalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS, ADDRESS)); freeNormalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("free_normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS)); } public String normalizeUrl(String url) { String result = null; try (Arena offHeap = Arena.ofConfined()) { MemorySegment originalUrl = offHeap.allocateFrom(url); MemorySegment pointer = offHeap.allocate(ValueLayout.ADDRESS); // URL正規化する処理の呼び出し normalizeUrl.invoke(originalUrl, pointer); // 正規化されたURL文字列を取得 AddressLayout unboundedLayout = ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE) ); MemorySegment normalizedUrl = pointer.get(unboundedLayout, 0L); result = normalizedUrl.getString(0L); // 解放処理 freeNormalizeUrl.invoke(pointer); } catch (Throwable e) { throw new RuntimeException(e); } return result; } 文字列は Arena::allocateFrom、また、加工された結果の文字列を受け取る引数には ValueLayout.ADDRESSを指定したMemorySegmentを取得します 実アプリケーションでのFFM APIサンプル URL正規化 メモリ割り当て、実行 public String normalizeUrl(String url) { String result = null; try (Arena offHeap = Arena.ofConfined()) { MemorySegment originalUrl = offHeap.allocateFrom(url); MemorySegment pointer = offHeap.allocate(ValueLayout.ADDRESS); // URL正規化する処理の呼び出し normalizeUrl.invoke(originalUrl, pointer); // 正規化されたURL文字列を取得 AddressLayout unboundedLayout = ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE) ); MemorySegment normalizedUrl = pointer.get(unboundedLayout, 0L); result = normalizedUrl.getString(0L); // 解放処理 freeNormalizeUrl.invoke(pointer); } catch (Throwable e) { throw new RuntimeException(e); } return result; } 該当箇所 FFM APIを用いたコード
  29. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 29 private static final MethodHandle normalizeUrl; private static final MethodHandle freeNormalizeUrl; static { File file = new File("..."); // nativeライブラリのファイルパス System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); normalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS, ADDRESS)); freeNormalizeUrl = Linker .nativeLinker() .downcallHandle(libs.find("free_normalize_url").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS)); } public String normalizeUrl(String url) { String result = null; try (Arena offHeap = Arena.ofConfined()) { MemorySegment originalUrl = offHeap.allocateFrom(url); MemorySegment pointer = offHeap.allocate(ValueLayout.ADDRESS); // URL正規化する処理の呼び出し normalizeUrl.invoke(originalUrl, pointer); // 正規化されたURL文字列を取得 AddressLayout unboundedLayout = ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE) ); MemorySegment normalizedUrl = pointer.get(unboundedLayout, 0L); result = normalizedUrl.getString(0L); // 解放処理 freeNormalizeUrl.invoke(pointer); } catch (Throwable e) { throw new RuntimeException(e); } return result; } void**より結果の文字列を取得する場合は、AddressLayoutと合わせて該当のMemorySegmentを 取得し、 MemorySegment::getString で値を取得します 実アプリケーションでのFFM APIサンプル URL正規化 メモリ割り当て、実行 public String normalizeUrl(String url) { String result = null; try (Arena offHeap = Arena.ofConfined()) { MemorySegment originalUrl = offHeap.allocateFrom(url); MemorySegment pointer = offHeap.allocate(ValueLayout.ADDRESS); // URL正規化する処理の呼び出し normalizeUrl.invoke(originalUrl, pointer); // 正規化されたURL文字列を取得 AddressLayout unboundedLayout = ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE) ); MemorySegment normalizedUrl = pointer.get(unboundedLayout, 0L); result = normalizedUrl.getString(0L); // 解放処理 freeNormalizeUrl.invoke(pointer); } catch (Throwable e) { throw new RuntimeException(e); } return result; } 該当箇所 FFM APIを用いたコード
  30. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 30 2つ目の例として、高速な機械学習推論のための自作nativeライブラリの例を紹介します。 intのidとdouble配列の特徴量(ドメイン、曜日など150個程度)を渡す例です 実アプリケーションでのFFM APIサンプル 機械学習推論 /* * @brief 推論を行い、scoreを戻す * @param [in] model_id model_id * @param [in] features 特徴量配列 * @param [in] size 特徴量配列の数 * @return score */ double nativePredict(int model_id, double features[], int size); モデルID:100 特徴量(example.com、土曜 etc...) score = 0.5 Java App 機械学習 推論 Nativeライブラリ I/F 処理イメージ nativePredict
  31. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 31 実アプリケーションでのFFM APIサンプル 機械学習推論 FFM APIを用いたコード Nativeライブラリ I/F private static final MethodHandle nativePredict; static { File file = // nativeライブラリのファイルパス(.so, .dylib etc) System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); nativePredict = Linker .nativeLinker() .downcallHandle( libs.find("nativePredict").orElseThrow(), FunctionDescriptor.of(JAVA_DOUBLE, JAVA_INT, ADDRESS, JAVA_INT) ); } public double nativePredict(int modelId, double[] features, int length) { try (Arena offHeap = Arena.ofConfined()) { MemorySegment mfeatures = offHeap.allocateFrom(JAVA_DOUBLE, features); double ret = (double) nativePredict.invoke(modelId, mfeatures, length); return ret; } catch (Throwable e) { throw new ApplicationException(e); } } 2つ目の例として、高速な機械学習推論のための自作nativeライブラリの例を紹介します。 intのidとdouble配列の特徴量(ドメイン、曜日など150個程度)を渡す例です /* * @brief 推論を行い、scoreを戻す * @param [in] model_id model_id * @param [in] features 特徴量配列 * @param [in] size 特徴量配列の数 * @return score */ double nativePredict(int model_id, double features[], int size);
  32. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 32 private static final MethodHandle nativePredict; static { File file = // nativeライブラリのファイルパス(.so, .dylib etc) System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); nativePredict = Linker .nativeLinker() .downcallHandle( libs.find("nativePredict").orElseThrow(), FunctionDescriptor.of(JAVA_DOUBLE, JAVA_INT, ADDRESS, JAVA_INT) ); } public double nativePredict(int modelId, double[] features, int length) { try (Arena offHeap = Arena.ofConfined()) { MemorySegment mfeatures = offHeap.allocateFrom(JAVA_DOUBLE, features); double ret = (double) nativePredict.invoke(modelId, mfeatures, length); return ret; } catch (Throwable e) { throw new ApplicationException(e); } } private static final MethodHandle nativePredict; static { File file = // nativeライブラリのファイルパス(.so, .dylib etc) System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); nativePredict = Linker .nativeLinker() .downcallHandle( libs.find("nativePredict").orElseThrow(), FunctionDescriptor.of( JAVA_DOUBLE, JAVA_INT, ADDRESS, JAVA_INT) ); } FFM APIを用いたコード 自作ライブラリのため、URL正規化と同様に SymbolLookup::loaderLookup で解決します 実アプリケーションでのFFM APIサンプル 機械学習推論 外部関数のルックアップ 該当箇所
  33. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 33 private static final MethodHandle nativePredict; static { File file = // nativeライブラリのファイルパス(.so, .dylib etc) System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); nativePredict = Linker .nativeLinker() .downcallHandle( libs.find("nativePredict").orElseThrow(), FunctionDescriptor.of( JAVA_DOUBLE, JAVA_INT, ADDRESS, JAVA_INT) ); } FunctionDescriptorおよびValueLayoutで関数を定義します。 引数のうち、double配列には ADDRESSを指定します 実アプリケーションでのFFM APIサンプル 機械学習推論 外部関数の定義 該当箇所 Nativeライブラリ I/F /* * @brief 推論を行い、scoreを戻す * @param [in] model_id model_id * @param [in] features 特徴量配列 * @param [in] size 特徴量配列の数 * @return score */ double nativePredict(int model_id, double features[], int size);
  34. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 34 private static final MethodHandle nativePredict; static { File file = // nativeライブラリのファイルパス(.so, .dylib etc) System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); nativePredict = Linker .nativeLinker() .downcallHandle( libs.find("nativePredict").orElseThrow(), FunctionDescriptor.of(JAVA_DOUBLE, JAVA_INT, ADDRESS, JAVA_INT) ); } public double nativePredict(int modelId, double[] features, int length) { try (Arena offHeap = Arena.ofConfined()) { MemorySegment mfeatures = offHeap.allocateFrom(JAVA_DOUBLE, features); double ret = (double) nativePredict.invoke(modelId, mfeatures, length); return ret; } catch (Throwable e) { throw new ApplicationException(e); } } URL正規化と同様にWebアプリケーションでの利用のため、 Arena::ofConfined 、try文で速やかにメモリ開放がされるようにします 実アプリケーションでのFFM APIサンプル 機械学習推論 メモリのライフタイムの指定 public double nativePredict(int modelId, double[] features, int length) { try (Arena offHeap = Arena.ofConfined()) { MemorySegment mfeatures = offHeap.allocateFrom( JAVA_DOUBLE, features); double ret = (double) nativePredict.invoke( modelId, mfeatures, length ); return ret; } catch (Throwable e) { throw new ApplicationException(e); } } 該当箇所 FFM APIを用いたコード
  35. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 35 引数がint(プリミティブ型)の場合は、そのまま外部関数の引数に渡すことができます。 double配列の場合は、Arena::allocateFromよりMemorySegmentを取得します 実アプリケーションでのFFM APIサンプル 機械学習推論 メモリ割り当て、実行 該当箇所 FFM APIを用いたコード private static final MethodHandle nativePredict; static { File file = // nativeライブラリのファイルパス(.so, .dylib etc) System.load(file.getAbsolutePath()); SymbolLookup libs = SymbolLookup.loaderLookup(); nativePredict = Linker .nativeLinker() .downcallHandle( libs.find("nativePredict").orElseThrow(), FunctionDescriptor.of(JAVA_DOUBLE, JAVA_INT, ADDRESS, JAVA_INT) ); } public double nativePredict(int modelId, double[] features, int length) { try (Arena offHeap = Arena.ofConfined()) { MemorySegment mfeatures = offHeap.allocateFrom(JAVA_DOUBLE, features); double ret = (double) nativePredict.invoke(modelId, mfeatures, length); return ret; } catch (Throwable e) { throw new ApplicationException(e); } } public double nativePredict(int modelId, double[] features, int length) { try (Arena offHeap = Arena.ofConfined()) { MemorySegment mfeatures = offHeap.allocateFrom( JAVA_DOUBLE, features); double ret = (double) nativePredict.invoke( modelId, mfeatures, length ); return ret; } catch (Throwable e) { throw new ApplicationException(e); } }
  36. 36 Copyright © 2011-2023 UL Systems, Inc. All rights reserved.

    - Proprietary & Confidential - 実アプリケーションでのベンチマーク比較
  37. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 37 JDK21, 22 URL正規化 : JNA から FFM APIへ 機械学習推論: JNR から FFM APIへ 指標:AverageTime これまで見てきた、URL正規化と機械学習推論の2つのNativeライブラリ呼び出しのベンチマーク 結果を共有します。 ベンチマークは、JMH(Java Microbenchmark Harness)および実際のアプリケーションで、 JNA/JNRからFFM APIに置き換えたもので計測した結果になります 実アプリケーションでのベンチマーク比較 ベンチマーク環境 サーバー JMH アプリケーション OS Linux CPU/Mem 52コア/128GB 計測 FFM API, JNR,JNAの3パターン 指標:AverageTime 計測 環境 環境 JDK22 Javaメモリ:30GB Web App(netty) 156スレッド/10k QPS
  38. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 38 JDK21, 22 URL正規化 : JNA から FFM APIへ 機械学習推論: JNR から FFM APIへ 指標:AverageTime これまで見てきた、URL正規化と機械学習推論の2つのNativeライブラリ呼び出しのベンチマーク 結果を共有します。 ベンチマークは、JMH(Java Microbenchmark Harness)および実際のアプリケーションで、 JNA/JNRからFFM APIに置き換えたもので計測した結果になります 実アプリケーションでのベンチマーク比較 ベンチマーク環境 サーバー アプリケーション OS Linux CPU/Mem 52コア/128GB 計測 FFM API, JNR,JNAの3パターン 指標:AverageTime 計測 環境 環境 JDK22 Javaメモリ:30GB Web App(netty) 156スレッド/10k QPS JMH
  39. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 39 JDK21, 22 URL正規化 : JNA から FFM APIへ 機械学習推論: JNR から FFM APIへ 指標:AverageTime これまで見てきた、URL正規化と機械学習推論の2つのNativeライブラリ呼び出しのベンチマーク 結果を共有します。 ベンチマークは、JMH(Java Microbenchmark Harness)および実際のアプリケーションで、 JNA/JNRからFFM APIに置き換えたもので計測した結果になります 実アプリケーションでのベンチマーク比較 ベンチマーク環境 サーバー アプリケーション OS Linux CPU/Mem 52コア/128GB 計測 FFM API, JNR,JNAの3パターン 指標:AverageTime 計測 環境 環境 JDK22 Javaメモリ:30GB Web App(netty) 156スレッド/10k QPS JMH
  40. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 40 URL正規化、機械学習推論ともに、JDK22 + FFM API が最も高速となりました (指標はAverageTimeですので、数値が低いほど高速となります) 実アプリケーションでのベンチマーク比較 JMHベンチマーク結果 URL正規化 AverageTime 機械学習推論 AverageTime JDK21 JDK22(ea.22) 差分 FFM API 16,775 ns/op 12,931 ns/op - JNR 13,169 ns/op 13,172 ns/op 0.98倍 JNA 17,304 ns/op 17,415 ns/op 0.74倍 JDK21 JDK22(ea.22) 差分 FFM API 2,887 ns/op 2,693 ns/op - JNR 2,974 ns/op 2,971 ns/op 0.90倍 JNA 4,269 ns/op 4,064 ns/op 0.66倍
  41. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 41 URL正規化、機械学習推論ともに、JDK22 + FFM API が最も高速となりました (指標はAverageTimeですので、数値が低いほど高速となります) 実アプリケーションでのベンチマーク比較 JMHベンチマーク結果 URL正規化 AverageTime 機械学習推論 AverageTime JDK21 JDK22(ea.22) 差分 FFM API 16,775 ns/op 12,931 ns/op - JNR 13,169 ns/op 13,172 ns/op 0.98倍 JNA 17,304 ns/op 17,415 ns/op 0.74倍 JDK21 JDK22(ea.22) 差分 FFM API 2,887 ns/op 2,693 ns/op - JNR 2,974 ns/op 2,971 ns/op 0.90倍 JNA 4,269 ns/op 4,064 ns/op 0.66倍
  42. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 42 実アプリケーションにおいても、JMH同様、FFM API のほうが高速となりました (メトリクスを実装する箇所の都合上、外部関数呼び出し+αの処理時間となっています) 実アプリケーションでのベンチマーク比較 アプリケーションベンチマーク結果 URL正規化 機械学習推論 284,641 ns 328,938 ns 26,920 ns 28,312 ns FFM API FFM API JNA JNR
  43. Copyright © 2011-2023 UL Systems, Inc. All rights reserved. -

    Proprietary & Confidential - 43 FFM APIは、既存のライブラリであるJNA、JNRより手軽さはありませんが、メモリ管理、速度を 考えると、取り入れる価値のあるものだと考えています。 メモリ管理 Arenaによるライフタイムにより、GC管理より細かくメモリ管理を行うことができる 速度 ベンチマーク上は、既存のライブラリよりも高速 まとめ 手軽さ メモリ管理 速度 FFM API △: インターフェースベースではないが、 jextractによりグルーコードを生成できる ◦: Arenaによって、GC管理 より細かく管理を行える ◦: JNA、JNRと比べて最も高速 JNI ✕: ブリッジコードが必要 ✕: 課題あり ◦: JNA ◦: インターフェースベース ✕: JNIと同様 ✕: JNIのラッパーのため最も低速 JNR ◦: インターフェースベース △: 開放はGC任せ ◦: JNIとほぼ同等、JNAより高速