我看到Java源代码中使用了System.arraycopy本地方法,感到很惊讶。
当然,原因是这样更快。但是,这段代码能够利用哪些本地技巧使它更快呢?
为什么不只是循环遍历原始数组并将每个指针复制到新数组中-这显然不会那么慢和繁琐吧?
这段代码无法用Java编写。原生代码可以忽略或省略对象数组和基本类型数组之间的区别,而Java不能,至少效率不高。
另外,由于重叠数组所需的语义,它也无法使用单个memcpy()
函数编写。
memmove
吧。虽然我认为在这个问题的上下文中并没有太大的区别。 - Péter Török当然,这取决于具体实现。
HotSpot将其视为“内置函数”,并在调用点插入代码。 这是机器码,而不是缓慢的旧C代码。 这也意味着该方法签名的问题基本上会消失。
一个简单的复制循环足够简单,可以对其应用明显的优化措施,例如循环展开。 实际发生的事情再次取决于具体实现。
在我的测试中,对于复制多维数组,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
System.arraycopy
执行的是浅拷贝(仅复制对内部float[]
的引用),而您嵌套的for
循环执行的是深拷贝(按float
逐一复制)。使用System.arraycopy
对fooCpy[i][j]
的更改将反映在foo
中,但不会使用嵌套的for
循环。 - misbernerJIT编译器不太可能生成和手写的C代码一样高效的底层代码。使用低级别的C可以实现许多优化,这些优化对于通用JIT编译器来说几乎是不可能的。
参见此链接以获取一些手写C实现(memcpy,但原理相同)的技巧和速度比较:查看此Optimizing Memcpy improves speed
C版本基本上独立于数组成员的类型和大小。在Java中无法做到这一点,因为无法将数组内容作为原始内存块(例如指针)获取。
arraycopy
的某些子情况可以使用memcpy
/memmove
实现。其他情况需要对每个复制的元素进行运行时类型检查。 - Stephen CString
对象填充的Object[]
复制到一个String[]
中。请参见http://java.sun.com/javase/6/docs/api/java/lang/System.html#arraycopy(java.lang.Object,%20int,%20java.lang.Object,%20int,%20int)的最后一段。 - Stephen Cmemcpy
和memmove
都是O(n)的,但由于例如simd优化,它们会快几倍,因此您可以说它们是O(n/x),其中x取决于这些函数中使用的优化。 - ufoq