5

JNAを使用して、もともとFortranで書かれたネイティブコードを呼び出すJavaコードがあります。(これは数値ライブラリであり、多くの数学者が Fortran でコーディングを行っています。) これは.soライブラリにコンパイルされます。以下を参照してください。

コードで単体テストを行ったすべてで素晴らしい結果が得られましたが、複数のスレッドからコードを使用しようとすると、すべてが奇妙なエラーで失敗し始めました。次に、再入可能な Fortran コードに関するいくつかのことを調べたところ、使用していたライブラリにいくつかのグローバル変数 (SAVE関数が再度呼び出されたときに変数の値を記憶する Fortran のキーワード: fortran SAVE ステートメント)と同等のものがあることがわかりました。

今のところ、ライブラリへの呼び出しをsynchronizedブロックでラップしていますが、これによりパフォーマンスが大幅に低下しています。ライブラリを再入可能に再設計するにはかなりの労力がかかるように思えます (数千行の数値コードがあり、サブルーチンが実行されているときに値がどのように引き継がれるかは明確ではありません)。問題を回避する最善の方法は?私の想像では…

  • グローバル変数が事実上スレッドローカルになるように、各 Java スレッドが共有ライブラリの個別のコピーをメモリにロードするようにする方法はありますか? それは可能ですか?JNA のダイレクト バインディングまたはライブラリ バインディングがどのように機能するか、またそのように使用する方法があるかどうかはわかりません。
  • 異なる VM から呼び出されたとしても、それはまだ台無しにされますか? 確認するにはどうすればよいですか?
  • gfortran( )を取得gccして Fortran コードを再入可能にコンパイルする方法はありますか?
  • Fortran コードを再入可能にする手っ取り早い方法はありますか? どうやら変数をスタックに保持しているキーワードについて検索しましたRECURSIVEが、既存のコードと互換性がないようです。
  • 他に考えられる解決策はありますか?

複数の VM で問題がないことを確認しました。メモリを共有しないため、これは理にかなっています。それでもPITAであり、スレッドよりもはるかに不便です。

4

2 に答える 2

2

参考までに、これを行うために実装した次のクラスを共有したかっただけです。指定されたライブラリとインターフェースを受け取り、nコピーを作成し、JNA プロキシされたインターフェースを各コピーにマップします。次に、スレッドセーフ ロックを実装する別のプロキシされたインターフェースを返し、再入可能でプロセッサ数 1 まで実行できるバージョンを作成します。もっている。

public class LibraryReplicator<C> {

    final BlockingQueue<C> libQueue;
    final Class<C> interfaceClass;
    final C proxiedInterface;

    @SuppressWarnings("unchecked")
    public LibraryReplicator(URL libraryResource, Class<C> interfaceClass, int copies) throws IOException {
        if (!interfaceClass.isInterface()) 
            throw new RuntimeException(interfaceClass + "is not a valid interface to map to the library.");

        libQueue = new LinkedBlockingQueue<C>(copies);
        this.interfaceClass = interfaceClass;

        // Create copies of the file and map them to interfaces
        String orig = libraryResource.getFile();
        File origFile = new File(orig);
        for( int i = 0; i < copies; i++ ) {
            File copy = new File(orig + "." + i);
            Files.copy(origFile, copy);                     

            C libCopy = (C) Native.loadLibrary(copy.getPath(), interfaceClass);         
            libQueue.offer(libCopy); // This should never fail
        }               

        proxiedInterface = (C) Proxy.newProxyInstance(
                interfaceClass.getClassLoader(), 
                new Class[] { interfaceClass }, 
                new BlockingInvocationHandler());
    }

    public LibraryReplicator(URL libraryResource, Class<C> interfaceClass) throws IOException {
        this(libraryResource, interfaceClass, Runtime.getRuntime().availableProcessors());
    }

    public C getProxiedInterface() {
        return proxiedInterface;
    }

    /*
     * Invocation handler that uses the queue to grab locks and maintain thread safety.  
     */
    private class BlockingInvocationHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
            C instance = null;

            // Grab a copy of the library out of the queue          
            do {
                try { instance = libQueue.take(); }
                catch(InterruptedException e) {}
            } while(instance == null);

            // Invoke the method
            Object result = method.invoke(instance, args);

            // Return the library to the queue
            while(true) {
                try { libQueue.put(instance); break; }
                catch( InterruptedException e ) {} 
            } 

            return result;
        }       
    }

}

使用例は、静的初期化子の一部として次のようなものです。

MvnPackGenz lib = new LibraryReplicator<MvnPackGenz>(
        MvnPackGenz.class.getClassLoader().getResource("mvnpack.so"), 
        MvnPackGenz.class).getProxiedInterface();

これにより、ライブラリのコピーの束 (私の場合は 12) が作成され、そのlib上に再入可能に「見える」変数が作成され、複数のスレッドで安全に実行できます。

-rw-r--r-- 1 mao mao 50525 Sep 26 13:55 mvnpack.so
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.0
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.1
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.10
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.11
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.2
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.3
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.4
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.5
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.6
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.7
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.8
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.9

更新されたバージョンは、次の場所で確認できます。

https://github.com/mizzao/libmao/blob/master/src/main/java/net/andrewmao/misc/LibraryReplicator.java

于 2013-09-26T22:38:52.790 に答える
1

I'm not sure about each thread having a separate instance of the library, however here's what I did a number of years ago: Get the operating system to make in re-entrant for you.

I ended up creating a pool of application instances on the Unix machine, and communicating with them using network sockets - each process was listening on its own socket.

Even if the library is not re-entrant, starting it as a separate process will be ok. . . Perhaps you can write a thin unix wrapper around the library and communicate over your own proprietary protocol.

于 2013-01-28T03:46:54.467 に答える