为什么Java中的System.arraycopy是原生的?
我很惊讶地发现在Java源代码中System.arraycopy是一个本地方法。
当然是因为速度更快。 但是,能够使用它的代码的原生技巧是什么?
为什么不循环遍历原始数组并将每个指针复制到新数组中 – 当然这不是那么慢又麻烦?
在本地代码中,可以使用单个memcpy
/ memmove
来完成,而不是n个不同的复制操作。 性能差异很大。
它不能用Java编写。 本地代码可以忽略或消除Object和数组原语之间的区别。 Java不能做到这一点,至less没有效率。
而且不能用单个memcpy()
写入,因为重叠数组需要语义。
当然,这是依赖于实现的。
HotSpot会将其视为“内在”,并在呼叫站点插入代码。 那是机器代码,不是老的C代码。 这也意味着该方法的签名问题基本消失。
一个简单的复制循环很简单,明显的优化可以应用到它。 例如循环展开。 到底发生了什么又是执行依赖。
在我自己的testing中,复制multidimensional array的System.arraycopy()比循环的循环快10到20倍:
float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9] float[][] fooCpy = new float[foo.length][foo[0].length]; long lTime = System.currentTimeMillis(); System.arraycopy(foo, 0, fooCpy, 0, foo.length); System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms"); lTime = System.currentTimeMillis(); for (int i = 0; i < foo.length; i++) { for (int j = 0; j < foo[0].length; j++) { fooCpy[i][j] = foo[i][j]; } } System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms"); for (int i = 0; i < foo.length; i++) { for (int j = 0; j < foo[0].length; j++) { if (fooCpy[i][j] != foo[i][j]) { System.err.println("ERROR at " + i + ", " + j); } } }
这打印:
System.arraycopy() duration: 1 ms loop duration: 16 ms
有几个原因:
-
JIT不可能像手动编写的C代码那样生成有效的低级代码。 使用低级C可以实现大量的优化,这对于通用的JIT编译器来说是不可能的。
看到这个链接的手写C实现(memcpy,但原则是相同的)一些技巧和速度比较:检查此优化Memcpy提高速度
-
C版本几乎与数组成员的types和大小无关。 在java中不可能做同样的事情,因为没有办法将数组内容作为一个原始的内存块(例如指针)。