allocate()
还是 allocateDirect()
,这是个问题。
多年来,我一直认为由于 DirectByteBuffer
是在操作系统层面上的直接内存映射,使用 get/put 调用速度比 HeapByteBuffer
更快。直到现在我才对这种情况感兴趣并想要了解确切的细节。我想知道这两种类型的 ByteBuffer
哪种更快,以及在什么条件下。
allocate()
还是 allocateDirect()
,这是个问题。
多年来,我一直认为由于 DirectByteBuffer
是在操作系统层面上的直接内存映射,使用 get/put 调用速度比 HeapByteBuffer
更快。直到现在我才对这种情况感兴趣并想要了解确切的细节。我想知道这两种类型的 ByteBuffer
哪种更快,以及在什么条件下。
操作系统对内存区域执行I/O操作。就操作系统而言,这些内存区域是连续的字节序列。因此,只有字节缓冲区才有资格参与I/O操作。同时请注意,操作系统将直接访问进程的地址空间(在本例中为JVM进程)以传输数据。这意味着作为I/O操作目标的内存区域必须是连续的字节序列。在JVM中,字节数组可能不会被连续地存储在内存中,或者垃圾回收器可以随时移动它。在Java中,数组是对象,并且对象内部存储数据的方式可能因不同的JVM实现而异。
因此,引入了直接缓冲区的概念。直接缓冲区旨在与通道和本地I / O例程交互。它们尽最大努力将字节元素存储在通道可以用于直接或原始访问的内存区域中,通过使用本机代码告诉操作系统直接排空或填充内存区域。
直接字节缓冲区通常是进行I/O操作的最佳选择。按照设计,它们支持JVM可用的最有效的I/O机制。非直接字节缓冲区可以传递给通道,但这样做可能会导致性能损失。通常不可能将非直接缓冲区作为本地I / O操作的目标。如果您将非直接ByteBuffer对象传递给通道进行写入,则通道可能隐式执行以下操作:
- 创建一个临时的直接ByteBuffer对象。
- 将非直接缓冲区的内容复制到临时缓冲区。
- 使用临时缓冲区执行低级 I/O 操作。
- 临时缓冲区对象超出范围并最终被垃圾回收。
这可能导致在每次 I/O 操作时进行缓冲拷贝和对象变化,而这正是我们想避免的。然而,根据实现方式,情况可能不会这么糟糕。运行时很可能会缓存和重用直接缓冲区或执行其他聪明的技巧来提高吞吐量。如果您只是为一次性使用创建缓冲区,则差异并不显著。另一方面,如果您将在高性能场景中反复使用缓冲区,则最好分配直接缓冲区并重用它们。
直接缓冲区对于 I/O 是最优的,但它们可能比非直接字节缓冲区更昂贵。直接缓冲区使用的内存是通过调用本机、操作系统特定的代码来分配的,绕过了标准 JVM 堆。建立和撤销直接缓冲区可能会比常驻堆的缓冲区显著更昂贵,这取决于主机操作系统和 JVM 实现。直接缓冲区的内存存储区域不受垃圾回收的影响,因为它们在标准 JVM 堆之外。
使用直接与非直接缓冲区的性能权衡可以因 JVM、操作系统和代码设计而大不相同。通过将内存分配到堆之外,您可能会使应用程序受到 JVM 不知道的其他力量的影响。当引入其他运动部件时,请确保您正在实现所需的效果。我建议遵循旧软件规范:先让它工作,再让它快。不要过于担心优化前期;首先要专注于正确性。JVM 实现可能能够执行缓冲区高速缓存或其他优化,从而为您提供最佳的性能。
你可以轻松获得所需的性能,而无需花费过多不必要的努力。
没有理由认为直接缓冲区在JVM内部的访问速度会更快。它们的优势在于当您将它们传递给本地代码时--例如,各种通道背后的代码。
由于DirectByteBuffers是直接在操作系统级别上进行的内存映射
这不是正确的说法。它们只是普通的应用程序进程内存,但不会在Java垃圾收集期间被重新定位,这在JNI层面内部简化了很多事情。您所描述的适用于MappedByteBuffer。
使用get/put调用性能更快
这个结论并不是从前提得出的;前提是错误的;而且结论也是错误的。一旦进入JNI层面,它们就会更快,如果您正在从同一个DirectByteBuffer读取和写入数据,则它们会更快,因为数据根本不需要跨越JNI边界。
最好自己进行测量。快速的答案似乎是,从 allocateDirect()
缓冲区发送数据所需的时间比allocate()
变体少25%到75%(测试将文件复制到/dev/null),具体取决于大小,但分配本身可能会慢得多(甚至可能慢100倍)。
参考资料:
SocketChannel
。因此,关于@bmargulies所说的,DirectByteBuffer
对于通道的性能表现更快。 - user238033