Java缓冲区类型与原生数组:哪个更快?
使用 C 语言开发时,必须手动分配和释放内存,这是一个容易出错的过程。相反,像 Java 这样的之后的一些语言通常会自动管理内存。Java 依赖于垃圾回收。实际上,内存是根据需要来分配,然后 Java 发现哪些数据不再访问,并回收相应的内存。垃圾回收过程既快速又安全,但是它不是免费的:尽管进行了数十年的优化,但它仍然可能给开发人员带来一些麻烦。
Java 具有原生数组(例如 int[] 类型),这些数组通常在 Java 堆上分配。也就是说,它们由 Java 作为动态数据进行分配和管理,并进行垃圾回收。
Java 还具有 Buffer 类型,如 IntBuffer,它是一种高级抽象,可以由原生 Java 数组实现,也可以由其他数据源(包括 Java 堆外数据)来实现。因此,可以使用 Buffer 类型来避免过多地依赖 Java 堆。
与原生数组相比,Buffer 会存在一定性能损失。我不是说 Buffer 慢。事实上,如果在 Buffer 和流(DataInputStream)之间做一个选择,你应该更倾向于 Buffer 类型。然而,根据我的经验,Buffer 在性能方面不如原生数组。
我可以用 new int[50000] 或 IntBuffer.allocate(50000) 来创建一个包含 50,000 个整数的数组。后者本质上应该是创建一个数组(在 Java 堆上),然后用 IntBuffer 接口来包装。
一个可能的直觉是,用高级接口包装一个数组应该是免费的。虽然高层次的抽象确实可以不带来性能上的损失(有时甚至可以带来性能上的提升),但是否会带来性能上的提升是一个经验问题。你绝对不应该只是假设你的抽象是免费得来的。
因为我在做一个经验陈述,但我可以用最简单的测试来证明它,比如给数组 以及 IntBuffer 中的每个元素加 1。
for(int k = 0; k < s.array.length; k++) {
s.array[k] += 1;
}
for(int k = 0; k < s.buffer.limit(); k++) {
s.buffer.put(k, s.buffer.get(k) + 1);
}
我在台式机(OpenJDK 14,4.2 GHz Intel 处理器)上得到结果如下:
也就是说,在此测试中,数组比 IntBuffers 快 4 倍以上。
如果有兴趣,你也可以自己运行以下基准测试。
https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/2020/11/30
我的看法是,Java 针对数组的许多优化不适用于 Buffer 类型。
当然,我们无法得知 Buffer 从 Java 堆外映射时发生了什么,根据我的经验,情况可能很糟。
Buffer 类型并不会让原生数组过时,至少在性能方面。
英文原文(包含一些精彩评论):
https://lemire.me/blog/2020/11/30/java-buffer-types-versus-native-arrays-which-is-faster/