比较直接和非直接ByteBuffer get / put操作

从直接bytebuffer获取/放置非直接bytebuffer比获取/放入更快吗?

如果我必须从直接bytebuffer读/写,最好先读/写一个线程本地字节数组,然后用字节数组更新(写入)直接bytebuffer?

从直接bytebuffer获取/放置非直接bytebuffer比获取/放入更快吗?

如果您正在将堆缓冲区与不使用本机字节顺序的直接缓冲区进行比较(大多数系统是小端,而直接ByteBuffer的默认值是大端),则性能非常相似。

如果使用本机有序字节缓冲区,则对于多字节值,性能可能会明显提高。 对于byte ,无论你做什么,它都没什么区别。

在HotSpot / OpenJDK中,ByteBuffer使用Unsafe类,许多本native方法被视为内在函数 。 这是依赖于JVM的,而AFAIK是Android VM在最近的版本中将其视为内在的。

如果转储生成的程序集,您可以在一个机器代码指令中看到Unsafe中的内在函数。 即他们没有JNI呼叫的开销。

实际上,如果您进行微调,您可能会发现ByteBuffer getXxxx或setXxxx的大部分时间都花在边界检查上,而不是实际的内存访问。 出于这个原因,我必须直接使用Unsafe 才能获得最大性能(注意:Oracle不鼓励这样做)

如果我必须从直接bytebuffer读取/写入,首先读取/写入线程本地字节数组然后更新(用于写入)直接字节缓冲区与字节数组完全更好吗?

我不想看到什么比这更好。 ;)听起来很复杂。

通常,最简单的解决方案更好,更快。


您可以使用此代码自行测试。

 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.2 ns。


为了certificate它不是“JNI”,而是围绕它引起延迟的guff。 您可以直接使用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缓存速度。 ;)

全部运行在i3 3.3 GHz上

直接缓冲区将数据保存在JNI域中,因此get()和put()必须跨越JNI边界。 非直接缓冲区将数据保存在JVM域中。

所以:

  1. 如果您没有在Java领域中使用数据,例如只是将通道复制到另一个通道,则直接缓冲区更快,因为数据根本不必跨越JNI边界。

  2. 相反,如果您正在使用Java land中的数据,则非直接缓冲区将更快。 它的重要性取决于数据跨越JNI边界的数量以及每次传输的量子数量。 例如,从/向直接缓冲区一次获取或放置一个字节可能会非常昂贵,一次获取/放置16384个字节会大大减少JNI边界成本。

要回答你的第二段,我会使用一个本地的byte []数组,而不是一个线程局部的,但是如果我在Java中使用数据,我根本就不会使用直接的字节缓冲区。 正如Javadoc所说,直接字节缓冲区应仅用于可提供可衡量的性能优势的地方。