为什么System.arraycopy是Java固有的?


84

我很惊讶在Java源代码中看到System.arraycopy是本机方法。

当然,原因是因为它速度更快。但是,能够采用哪些本机技巧使代码更快呢?

为什么不只是循环遍历原始数组并将每个指针复制到新数组-当然这不是那么慢且麻烦吗?

Answers:


82

在本机代码中,可以使用单个memcpy/来完成,这与n个不同的复制操作memmove相反。性能差异很大。


@Peter,因此您可以在本机代码中使用Java内存模型吗?(我从来没有任何理由做任何本地的malarkey)
James

8
实际上,只能arraycopy使用memcpy/来实现的某些子情况memmove。其他要求对复制的每个元素进行运行时类型检查。
斯蒂芬·C

1
@Stephen C,有趣-为什么?
彼得Török

3
@PéterTörök-考虑从Object[]填充了String对象的对象复制到String[]。参见java.sun.com/javase/6/docs/api/java/lang/…的
Stephen C 2010年

3
Peter,Object []和byte [] + char []是最常复制的,它们都不要求显式的类型检查。编译器非常聪明,除非需要,否则无需检查,实际上,在99.9%的情况下,它不需要检查。有趣的是,小型副本(少于高速缓存行)非常占主导地位,因此,快速存储小型副本的“ memcpy”确实很重要。
bestsss 2011年

16

它不能用Java编写。本机代码能够忽略或消除Object数组与基本数组之间的差异。Java无法做到这一点,至少没有效率。

而且,由于重叠数组需要语义,因此不能用单个memcpy()语言编写。


5
很好,memmove那就这样。尽管我认为在此问题的上下文中并没有多大区别。
彼得Török

也不是memmove(),请参阅@Stephen C对另一个答案的评论。
罗恩侯爵,

已经看到了,因为那恰好是我自己的回答;-)但是还是谢谢。
彼得Török

1
@Geek重叠的数组。如果源数组和目标数组以及相同且只有偏移量不同,则将仔细指定行为,并且memcpy()不符合要求。
洛恩侯爵

1
不能用Java编写吗?一个人不能写一个通用方法来处理Object的子类,然后为每个基本类型写一个吗?
Michael Dorst,2013年

10

当然,这取决于实现。

HotSpot会将其视为“本征”,并在呼叫站点插入代码。那是机器代码,而不是慢的旧C代码。这也意味着该方法签名的问题将基本消失。

一个简单的复制循环非常简单,可以对其进行明显的优化。例如循环展开。确切的是,再次取决于实现。


2
这是一个非常体面的答案:),尤其是。提到内在函数。W / O他们,因为它通常反正由JIT展开简单的迭代可能会更快
bestsss

4

在我自己的测试中,用于复制多维数组的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

9
即使这个问题很古老,也仅作记录:这不是一个公平的基准(更不用说这个基准是否首先有意义的问题了)。System.arraycopy执行浅表复制(仅复制对内部s的引用float[]),而您的嵌套for-loops执行深表复制(floatby float)。对的更改fooCpy[i][j]将在foousing中反映出来System.arraycopy,但不会使用嵌套的for-loops。
13年

4

有几个原因:

  1. JIT不太可能像手动编写的C代码那样生成高效的低级代码。使用低级C可以实现很多优化,而这些优化对于通用JIT编译器几乎是不可能的。

    请参见此链接,以了解一些手写C实现的技巧和速度比较(memcpy,但原理相同):选中此优化Memcpy可以提高速度

  2. C版本几乎与数组成员的类型和大小无关。在Java中不可能做同样的事情,因为没有办法将数组内容作为原始的内存块(例如指针)获取。


1
Java代码可以得到优化。其实真正发生的是生成的机器代码的效率比的C.
汤姆Hawtin - tackline

我同意,有时JITed代码会更好地进行本地优化,因为它知道运行在哪个处理器上。但是,由于它是“及时的”,它将永远无法使用所有花费较长时间才能执行的非局部优化。而且,它将永远无法匹配手工制作的C代码(通过编译特定的处理器或通过某种运行时检查,它也可能考虑到处理器并部分抵消了JIT的优势)。
HrvojePrgeša,2010年

1
我认为Sun JIT编译器团队会对其中的许多观点提出异议。例如,我相信HotSpot会进行全局优化以消除不必要的方法分派,并且没有理由JIT无法生成处理器特定的代码。还有一点是,JIT编译器可以根据当前应用程序运行的执行行为进行分支优化。
斯蒂芬·C

@Stephen C-关于分支优化的出色点,尽管如此,您也可以使用C / C ++编译器执行静态性能分析,以达到类似的效果。我还认为该热点具有两种操作模式-桌面应用程序将不使用所有可用的优化来实现合理的启动时间,而服务器应用程序将更积极地进行优化。总而言之,您将获得一些优势,但同时也会失去一些优势。
HrvojePrgeša,2010年

1
System.arrayCopy没有使用C实现,这使该答案无效
Nitsan Wakart
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.