使用 System.arraycopy(...) 比使用 for 循环复制数组更好吗?

Posted

技术标签:

【中文标题】使用 System.arraycopy(...) 比使用 for 循环复制数组更好吗?【英文标题】:Is it better to use System.arraycopy(...) than a for loop for copying arrays? 【发布时间】:2013-09-05 14:15:22 【问题描述】:

我想创建一个新的对象数组,将两个较小的数组放在一起。

它们不能为空,但大小可以为 0。

我无法在这两种方式之间进行选择:它们是等效的还是更有效的一种(例如 system.arraycopy() 复制整个块)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) 
    if (i<publicThings.length)
        things[i] = publicThings[i]
     else 
        things[i] = privateThings[i-publicThings.length]        
    

唯一的区别是代码的外观吗?

编辑:感谢链接问题,但他们似乎有一个未解决的讨论:

it is not for native types: byte[], Object[], char[] 真的更快吗?在所有其他情况下,都会执行类型检查,这将是我的情况,因此将是等效的......不是吗?

在另一个链接的问题上,他们说the size matters a lot,对于大小 >24 system.arraycopy() 获胜,对于小于 10,手动 for 循环更好...

现在我真的很困惑。

【问题讨论】:

arraycopy() 是本机调用,肯定更快。 您是否尝试过对两种不同的实现进行基准测试? 看这里:***.com/questions/2772152/… 你应该选择你认为最易读和将来最容易维护的那个。只有当你确定这是瓶颈的根源时,你才应该改变你的方法。 不要重新发明***! 【参考方案1】:
public void testHardCopyBytes()

    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    
        out[i] = bytes[i];
    


public void testArrayCopyBytes()

    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);

我知道 JUnit 测试并不是真正适合进行基准测试,但是 testHardCopyBytes 耗时 0.157 秒完成 和 testArrayCopyBytes 完成耗时 0.086 秒。 我认为这取决于虚拟机,但它看起来好像复制内存块而不是复制单个数组元素。这绝对会提高性能。

编辑: 看起来 System.arraycopy 的表现无处不在。 当使用字符串而不是字节,并且数组很小(大小为 10)时, 我得到了这些结果:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

这是数组大小为 0x1000000 时的样子。看起来 System.arraycopy 肯定会以更大的数组获胜。

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

多么奇特! 感谢 Daren 指出引用的复制方式不同。它使这成为一个更有趣的问题!

【讨论】:

感谢您的努力,但您错过了看似关键的点:非本机类型(使用 anythign 创建一个随机类,以便数组包含引用)和大小...似乎较小的数组大小,手动for循环更快。想纠正这个问题吗? 哇哦,你是对的!这很有趣。在这些数组中放置字符串而不是字节会产生巨大的差异:>> >> 那你得到了什么结果?同样有趣的是尝试使用数组大小​​= 10 ...谢谢! (我希望我在这里有我的 IDE,我在没有编译器的情况下进行编码)。 现在我已经将它们包装在一些 System.nanoTime() 调用中并设置 size = 10 以查看每个需要多少纳秒。看起来对于小的原始数组,循环更好;对于参考,arrayCopy 更好。: >> >> >> >>跨度> 非常有趣的结果!太感谢了!您能否编辑您的答案以包含此内容,我很乐意接受它,因此它始终是所有人看到的第一个……您已经获得了我的投票。 :)【参考方案2】:

Arrays.copyOf(T[], int) 更容易阅读。 它在内部使用System.arraycopy(),这是一个本地调用。

你不能更快地得到它!

【讨论】:

看来你可以依赖很多东西,但是感谢你指出我不知道的功能,而且确实更容易阅读。 :) 是的!正如@Svetoslav Tsolov 所说,这取决于很多事情。我只是想指出 Arrays.copyOf copyOf 不能总是替换 arraycopy,但它适合这个用例。 NB 如果您正在考虑性能,那么它不会像 System.arraycopy() 那样快,因为它需要内存分配。如果这是在一个循环中,那么重复分配将导致垃圾收集,这将对性能造成巨大影响。 @PhilippSander 只是为了检查我是不是愚蠢,我在我的游戏循环中添加了代码来复制一个 1MB 数组,这几乎不会触发 GC。使用 Array.copyOf() 我的 DVM 每秒调用 GC 5 次,游戏变得非常滞后。我认为可以肯定地说发生了内存分配。【参考方案3】:

这取决于虚拟机,但 System.arraycopy 应该为您提供最接近本机性能。

我作为嵌入式系统(性能是重中之重)的 Java 开发人员已经工作了 2 年,并且在任何可以使用 System.arraycopy 的地方,我主要使用它/看到它在现有代码中使用。当性能成为问题时,它总是优于循环。 如果性能不是一个大问题,我会选择循环。更容易阅读。

【讨论】:

数组大小和类型(基本与继承)之类的东西似乎会影响性能。 是的,它本身不是“本机性能”,这就是为什么我说我“主要”在可能的地方使用它(你会注意到它主要胜过循环复制)。我猜原因是:当它是一个原始类型的小数组时,“调用成本”大于性能提升。出于同样的原因,使用 JNI 会降低性能 - 本机代码本身很快,但从 java 进程调用它 - 并没有那么多。 小修正,Arrays.copy 不是 JNI,它是内在的。内在函数比 JNI 快得多。 JIT 编译器如何以及何时将其转换为内在函数取决于所使用的 JVM/编译器。 Arrays.copy 不存在,Arrays.copyOf 是一个库函数。【参考方案4】:

我没有依赖推测和可能过时的信息,而是使用caliper 运行了一些基准测试。事实上,Caliper 附带了一些示例,包括一个 CopyArrayBenchmark 可以准确测量这个问题!你所要做的就是运行

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

我的结果基于 Oracle 的 Java HotSpot(TM) 64 位服务器 VM,1.8.0_31-b13,在 2010 年中期的 MacBook Pro(macOS 10.11.6 和 Intel Arrandale i7,8 GiB RAM)上运行。我认为发布原始计时数据没有用。相反,我会用支持性的可视化来总结结论。

总结:

编写手动 for 循环将每个元素复制到新实例化的数组中从来都不是有利的,无论是对于短数组还是长数组。 Arrays.copyOf(<em>array</em>, <em>array</em>.length)<em>array</em>.clone() 都一直很快。这两种技术在性能上几乎相同;您选择哪一个是一个品味问题。 System.arraycopy(<em>src</em>, 0, <em>dest</em>, 0, <em>src</em>.length) 几乎与 Arrays.copyOf(<em>array</em>, <em>array</em>.length)<em>array</em>.clone() 一样快,但并非始终如此。 (请参阅 50000 ints 的案例。)正因为如此,以及调用的冗长,如果您需要精确控制哪些元素被复制到哪里,我建议您使用 System.arraycopy()

以下是时序图:

【讨论】:

int 复制有什么奇怪的地方吗?奇怪的是,它 arraycopy 在大规模整数上会很慢。【参考方案5】:

Arrays.copyOf 不可能比System.arraycopy 快,因为这是copyOf 的实现:

public static int[] copyOf(int[] original, int newLength) 
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;

【讨论】:

【参考方案6】:

执行像Arrays.copyOf(T[], int) 这样的本机方法确实有一些开销,但这并不意味着它不如使用JNI 执行它快。

最简单的方法是编写基准测试。

您可以检查Arrays.copyOf(T[], int) 是否比正常的for 循环更快。

来自here的基准代码:-

public void test(int copySize, int copyCount, int testRep) 
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) 
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    
    System.out.println();


public void copy(int copySize, int copyCount) 
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) 
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");


public void loop(int copySize, int copyCount) 
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) 
        for (int i = copySize - 1; i >= 0; --i) 
            dst[i] = src[i + 1];
        
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) 
            src[i] = dst[i];
        
        src[copySize] = dst[copySize];
    
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");


public int[] newSrc(int arraySize) 
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) 
        src[i] = i;
    
    return src;

System.arraycopy() 使用 JNI(Java 本地接口)来复制数组(或其中的一部分),因此速度非常快,您可以确认here

【讨论】:

这段代码使用 int[] 你可以用 String[] 试试吗(用不同的值初始化:“1”、“2”等,因为它们是不可变的 JNI is very slow。 System.arraycopy 不使用它。 不,System.arraycopy 不使用 JNI,它仅用于调用第三方库。相反,它是一个原生调用,这意味着在 VM 中有一个原生实现。【参考方案7】:

System.arraycopy() 是一个本机调用,它直接在内存中进行复制操作。单个内存副本总是比你的 for 循环快

【讨论】:

我已经读过,对于非本地类型(任何创建的类,比如我的),它可能效率不高……而对于小尺寸(我的情况),手动 for 循环可能会更好。 ..愿意发表评论吗? 确实,System.arraycopy() 有一些开销,因此对于小型数组 (n = ~10),循环实际上更快

以上是关于使用 System.arraycopy(...) 比使用 for 循环复制数组更好吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 System.arraycopy(...) 比使用 for 循环复制数组更好吗?

复制数组之System.arraycopy()的使用

使用System.arraycopy()实现数组之间的复制

使用System.arraycopy()实现数组之间的复制

System.arraycopy方法实现数组的复制

使用System.arraycopy()实现数组之间的复制