11

非直接バイトバッファからのget/putは、直接バイトバッファからのget / putよりも高速ですか?

直接バイトバッファーから読み取り/書き込みを行う必要がある場合は、最初にスレッドローカルバイト配列に読み取り/書き込みを行ってから、直接バ​​イトバッファーをバイト配列で完全に更新(書き込み用)する方がよいでしょうか?

4

2 に答える 2

24

非直接バイトバッファからのget/putは、直接バイトバッファからのget / putよりも高速ですか?

ヒープバッファをネイティブバイトオーダーを使用しないダイレクトバッファと比較している場合(ほとんどのシステムはリトルエンディアンであり、ダイレクトByteBufferのデフォルトはビッグエンディアンです)、パフォーマンスは非常に似ています。

ネイティブの順序付きバイトバッファを使用する場合、マルチバイト値のパフォーマンスが大幅に向上する可能性があります。それbyteはあなたが何をしてもほとんど違いがないからです。

HotSpot / OpenJDKでは、ByteBufferはUnsafeクラスを使用し、メソッドの多くは組み込み関数nativeとして扱われます。これはJVMに依存しており、AndroidVMは最近のバージョンではこれを組み込みとして扱います。

生成されたアセンブリをダンプすると、Unsafeの組み込み関数が1つのマシンコード命令で変換されていることがわかります。つまり、JNI呼び出しのオーバーヘッドはありません。

実際、マイクロチューニングに興味がある場合は、ByteBuffer getXxxxまたはsetXxxxのほとんどの時間が、実際のメモリアクセスではなく、境界チェックに費やされていることに気付くかもしれません。このため、パフォーマンスを最大化する必要がある場合でも、Unsafeを直接使用します(注:これはOracleでは推奨されていません)

直接バイトバッファーから読み取り/書き込みを行う必要がある場合は、最初にスレッドローカルバイト配列に読み取り/書き込みを行ってから、直接バ​​イトバッファーをバイト配列で完全に更新(書き込み用)する方がよいでしょうか?

私はそれが何よりも優れているのを見たくありません。;)それは非常に複雑に聞こえます。

多くの場合、最も単純なソリューションの方が優れており、高速です。


このコードを使用して、これを自分でテストできます。

public static void main(String... args) {
    ByteBuffer bb1 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
    ByteBuffer bb2 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
    for (int i = 0; i < 10; i++)
        runTest(bb1, bb2);
}

private static void runTest(ByteBuffer bb1, ByteBuffer bb2) {
    bb1.clear();
    bb2.clear();
    long start = System.nanoTime();
    int count = 0;
    while (bb2.remaining() > 0)
        bb2.putInt(bb1.getInt());
    long time = System.nanoTime() - start;
    int operations = bb1.capacity() / 4 * 2;
    System.out.printf("Each putInt/getInt took an average of %.1f ns%n", (double) time / operations);
}

プリント

Each putInt/getInt took an average of 83.9 ns
Each putInt/getInt took an average of 1.4 ns
Each putInt/getInt took an average of 34.7 ns
Each putInt/getInt took an average of 1.3 ns
Each putInt/getInt took an average of 1.2 ns
Each putInt/getInt took an average of 1.3 ns
Each putInt/getInt took an average of 1.2 ns
Each putInt/getInt took an average of 1.2 ns
Each putInt/getInt took an average of 1.2 ns
Each putInt/getInt took an average of 1.2 ns

JNI呼び出しには1.2ns以上かかると確信しています。


それが「JNI」呼び出しではなく、遅延の原因となるその周りのガフであることを示すため。Unsafeを直接使用して同じループを作成できます。

public static void main(String... args) {
    ByteBuffer bb1 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
    ByteBuffer bb2 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
    for (int i = 0; i < 10; i++)
        runTest(bb1, bb2);
}

private static void runTest(ByteBuffer bb1, ByteBuffer bb2) {
    Unsafe unsafe = getTheUnsafe();
    long start = System.nanoTime();
    long addr1 = ((DirectBuffer) bb1).address();
    long addr2 = ((DirectBuffer) bb2).address();
    for (int i = 0, len = Math.min(bb1.capacity(), bb2.capacity()); i < len; i += 4)
        unsafe.putInt(addr1 + i, unsafe.getInt(addr2 + i));
    long time = System.nanoTime() - start;
    int operations = bb1.capacity() / 4 * 2;
    System.out.printf("Each putInt/getInt took an average of %.1f ns%n", (double) time / operations);
}

public static Unsafe getTheUnsafe() {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        return (Unsafe) theUnsafe.get(null);
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

プリント

Each putInt/getInt took an average of 40.4 ns
Each putInt/getInt took an average of 44.4 ns
Each putInt/getInt took an average of 0.4 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns
Each putInt/getInt took an average of 0.3 ns

nativeしたがって、この呼び出しは、JNI呼び出しで予想されるよりもはるかに高速であることがわかります。この遅延の主な理由は、L2キャッシュ速度である可能性があります。;)

すべてi33.3GHzで動作します

于 2012-06-24T09:47:15.203 に答える
2

直接バッファーはJNIランドのデータを保持するため、get()とput()はJNIの境界を越える必要があります。非直接バッファは、JVMランドのデータを保持します。

それで:

  1. あるチャネルを別のチャネルにコピーするなど、Javaランドでデータをまったく使用していない場合は、データがJNIの境界を越える必要がないため、直接バッファが高速になります。

  2. 逆に、Javaランドのデータで遊んでいる場合は、非直接バッファの方が高速です。その重要性は、JNI境界を越える必要のあるデータの量と、毎回転送されるクォンタムに依存します。たとえば、ダイレクトバッファから/へ一度に1バイトを取得または配置すると、非常にコストがかかる可能性があります。一度に16384バイトを取得/配置すると、JNI境界コストが大幅に償却されます。

2番目の段落に答えるために、スレッドローカルではなくローカルbyte []配列を使用しますが、Javaランドのデータで遊んでいる場合は、直接バイトバッファーをまったく使用しません。Javadocが言うように、ダイレクトバイトバッファは、測定可能なパフォーマンス上の利点を提供する場合にのみ使用する必要があります。

于 2012-06-24T02:10:39.293 に答える