154

To allocate()or to allocateDirect(),这是个问题。

几年来,我一直坚持认为,由于DirectByteBuffers 是操作系统级别的直接内存映射,因此使用 get/put 调用它会比HeapByteBuffers 执行得更快。直到现在,我才真正有兴趣了解有关情况的确切细节。我想知道这两种ByteBuffers 中哪一种更快,在什么条件下。

4

4 回答 4

158

Ron Hitches 在他的优秀著作Java NIO中似乎提供了我认为可以很好地回答您的问题的内容:

操作系统对内存区域执行 I/O 操作。就操作系统而言,这些内存区域是连续的字节序列。因此,只有字节缓冲区有资格参与 I/O 操作也就不足为奇了。还记得操作系统将直接访问进程的地址空间,在这种情况下是 JVM 进程,以传输数据。这意味着作为 I/O 操作目标的内存区域必须是连续的字节序列。在 JVM 中,字节数组可能不会连续存储在内存中,或者垃圾收集器可以随时移动它。数组是 Java 中的对象,数据在该对象中的存储方式可能因 JVM 实现而异。

因此,引入了直接缓冲区的概念。直接缓冲区旨在与通道和本机 I/O 例程交互。他们尽最大努力将字节元素存储在通道可用于直接或原始访问的内存区域中,方法是使用本机代码告诉操作系统直接排空或填充内存区域。

直接字节缓冲区通常是 I/O 操作的最佳选择。按照设计,它们支持 JVM 可用的最有效的 I/O 机制。可以将非直接字节缓冲区传递给通道,但这样做可能会导致性能损失。非直接缓冲区通常不可能成为本机 I/O 操作的目标。如果将非直接 ByteBuffer 对象传递给通道进行写入,则通道可能会在每次调用时隐式执行以下操作:

  1. 创建一个临时的直接 ByteBuffer 对象。
  2. 将非直接缓冲区的内容复制到临时缓冲区。
  3. 使用临时缓冲区执行低级 I/O 操作。
  4. 临时缓冲区对象超出范围并最终被垃圾回收。

这可能会导致每个 I/O 上的缓冲区复制和对象搅动,这正是我们想要避免的那种事情。但是,根据实施情况,事情可能没有这么糟糕。运行时可能会缓存和重用直接缓冲区或执行其他巧妙的技巧来提高吞吐量。如果您只是创建一个一次性使用的缓冲区,则差异并不显着。另一方面,如果您将在高性能场景中重复使用缓冲区,则最好分配直接缓冲区并重用它们。

直接缓冲区是 I/O 的最佳选择,但创建它们可能比非直接字节缓冲区更昂贵。直接缓冲区使用的内存是通过调用本机特定于操作系统的代码来分配的,绕过标准的 JVM 堆。设置和拆除直接缓冲区可能比堆驻留缓冲区更昂贵,具体取决于主机操作系统和 JVM 实现。直接缓冲区的内存存储区域不受垃圾回收的影响,因为它们位于标准 JVM 堆之外。

使用直接缓冲区与非直接缓冲区的性能权衡可能因 JVM、操作系统和代码设计而异。通过在堆外分配内存,您的应用程序可能会受到 JVM 不知道的额外压力。在使用其他移动部件时,请确保您达到了预期的效果。我推荐旧的软件格言:先让它工作,然后让它快速。不要太担心前期的优化;首先关注正确性。JVM 实现可能能够执行缓冲区缓存或其他优化,从而为您提供所需的性能,而无需您付出很多不必要的努力。

于 2011-04-15T02:48:22.017 回答
26

没有理由期望直接缓冲区在 jvm内部的访问速度更快。当您将它们传递给本机代码时,它们的优势就出现了——例如,各种通道背后的代码。

于 2011-04-15T01:20:24.727 回答
22

因为 DirectByteBuffers 是操作系统级别的直接内存映射

他们不是。它们只是普通的应用程序进程内存,但在 Java GC 期间不受重定位,这大大简化了 JNI 层内部的事情。您所描述的适用于MappedByteBuffer.

它会通过 get/put 调用更快地执行

结论不是从前提得出的;前提是错误的;并且结论也是错误的。一旦进入 JNI 层,它们会更快,如果您从同一层读取和写入,DirectByteBuffer它们会更快,因为数据根本不必跨越 JNI 边界。

于 2011-04-17T01:56:33.350 回答
19

最好自己测量。快速回答似乎是,从allocateDirect()缓冲区发送所需的时间比allocate()变体(测试为将文件复制到 /dev/null)少 25% 到 75%,具体取决于大小,但分配本身可能会慢得多(即使100 倍)。

资料来源:

于 2011-04-15T00:18:40.643 回答