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

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

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. Foreign Function & Memory APIを用いた
    Nativeライブラリ呼び出しと既存ライブラリの比較
    JJUG CCC 2023 Fall
    2023/11/11
    @hiroisojp

    View full-size slide

  2. 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. 実アプリケーションでのベンチマーク比較

    View full-size slide

  3. 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)

    View full-size slide

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

    View full-size slide

  5. 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)概要

    View full-size slide

  6. 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より高速

    View full-size slide

  7. 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より高速

    View full-size slide

  8. 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
    記載が大変なので割愛

    View full-size slide

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

    View full-size slide

  10. 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

    View full-size slide

  11. 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);
    }
    }

    View full-size slide

  12. 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を用いたコード
    関数のルックアップ

    View full-size slide

  13. 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の対応(抜粋)

    View full-size slide

  14. 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の種類

    View full-size slide

  15. 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文の外で操作する誤ったコード
    スタックトレース例

    View full-size slide

  16. 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);

    View full-size slide

  17. 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を用いたコード
    呼び出し例

    View full-size slide

  18. 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);
    }
    }
    }
    関数への値を渡す場合も
    直接渡す事が可能
    関数の定義は
    インターフェースで表現
    関数への値を渡す場合も
    直接渡す事が可能
    関数の定義は
    インターフェースで表現

    View full-size slide

  19. 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$);
    }
    }
    コマンド例
    生成コード例

    View full-size slide

  20. 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

    View full-size slide

  21. 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

    View full-size slide

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

    View full-size slide

  23. 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

    View full-size slide

  24. 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);

    View full-size slide

  25. 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);

    View full-size slide

  26. 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正規化 外部関数のルックアップ
    該当箇所

    View full-size slide

  27. 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

    View full-size slide

  28. 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を用いたコード

    View full-size slide

  29. 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を用いたコード

    View full-size slide

  30. 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を用いたコード

    View full-size slide

  31. 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

    View full-size slide

  32. 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);

    View full-size slide

  33. 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サンプル
    機械学習推論 外部関数のルックアップ
    該当箇所

    View full-size slide

  34. 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);

    View full-size slide

  35. 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を用いたコード

    View full-size slide

  36. 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);
    }
    }

    View full-size slide

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

    View full-size slide

  38. 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

    View full-size slide

  39. 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

    View full-size slide

  40. 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

    View full-size slide

  41. 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倍

    View full-size slide

  42. 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倍

    View full-size slide

  43. 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

    View full-size slide

  44. 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より高速

    View full-size slide