ByteBuffer.allocate()与ByteBuffer.allocateDirect()

135 java performance nio bytebuffer

allocate()或者allocateDirect(),这是一个问题.

多年以来我一直坚持认为,因为DirectByteBuffers是OS级别的直接内存映射,所以get/put调用的执行速度比HeapByteBuffers 快.到目前为止,我从未真正有兴趣了解有关情况的具体细节.我想知道两种类型的ByteBuffer哪种更快,哪种条件更快.

Edw*_*rzo 144

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实现可能能够执行缓冲区缓存或其他优化,这些优化将为您提供所需的性能,而无需您做出大量不必要的工作.

  • 我不喜欢那句话,因为它包含太多的猜测.此外,当为非直接的ByteBuffer执行IO时,JVM当然不需要分配直接的ByteBuffer:它足以在堆上malloc一个字节序列,执行IO,从字节复制到ByteBuffer并释放字节.这些区域甚至可以缓存.但是完全没必要为此分配Java对象.真正的答案只能从测量中获得.上次我做测量时没有显着差异.我将不得不重做测试以提出所有具体细节. (7认同)
  • 如果一本描述NIO(和本地操作)的书可以在其中具有确定性,那么这是值得怀疑的.毕竟,不同的JVM和操作系统以不同的方式管理事物,所以作者不能因无法保证某些行为而受到指责. (3认同)

bma*_*ies 25

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


use*_*421 18

因为DirectByteBuffers是OS级别的直接内存映射

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

它可以更快地执行get/put调用

结论不是从前提中得出的; 前提是假的; 结论也是错误的.他们更快一旦你的JNI层内,如果你正在阅读和同写DirectByteBuffer他们是快,因为数据从来没有越过边界JNI在所有.

  • 这是一个很好而重要的观点:在IO的路径上,你必须在*some*point处穿过Java-JNI边界.直接和非直接字节缓冲区仅移动边界:使用直接缓冲区来自Java域的所有put操作都必须交叉,而使用非直接缓冲区时,所有IO操作都必须交叉.什么是更快取决于应用程序. (5认同)

Rap*_*ien 17

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

资料来源: