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


89

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

它们不能为null,但大小可以为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循环更好。

现在我真的很困惑。


16
arraycopy()是一个本地电话,这肯定更快。
Sotirios Delimanolis 2013年

4
您是否尝试过对两个不同的实现进行基准测试?
亚历克斯


15
您应该选择将来最容易阅读且最容易维护的方式。仅当确定这是瓶颈的根源时,才应更改方法。
arshajii 2013年

1
不要重新发明轮子!
camickr 2013年

Answers:


87
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.157s,

testArrayCopyBytes 完成了0.086s。

我认为这取决于虚拟机,但看起来好像它复制内存块而不是复制单个数组元素。这将绝对提高性能。

编辑:
看起来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

多么奇特!

谢谢,达伦,指出引用不同。这使这个问题变得更加有趣!


2
感谢您的努力,但您错过了看似关键的要点:非本机类型(使用anythign创建一个随机类,因此该数组包含引用)和大小...似乎对于较小的数组大小,手动for循环更快。想要更正这个吗?
Daren 2013年

2
哇,你是对的!那很有趣。将字符串放置在那些数组中而不是字节中会产生巨大的不同:<<< testHardCopyStrs:0.161s >>> <<< testArrayCopyStrs:0.170s >>>
Trent Small

那你得到什么结果?同样有趣的是尝试使用数组大小​​= 10 ...谢谢!(我希望我的IDE在这里,我在没有编译器的情况下进行编码)。
Daren 2013年

好了,现在我将它们包装在一些System.nanoTime()调用中,并将size设置为10,以查看每次花费多少纳秒。看起来对于小的基本数组,循环会更好;为参考,arrayCopy更好:<<< testHardCopyBytes:4491 NS >>> <<< testHardCopyStrs:56778个纳秒>>> <<< testArrayCopyBytes:10265个纳秒>>> <<< testArrayCopyStrs:4490 NS >>>
特伦特小

非常有趣的结果!非常感谢!您能否编辑您的答案以包括此答案,我会很乐意接受,因此它始终是有史以来第一个看到您的人……您已经投票支持我。:)
Daren 2013年

36

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

您不能更快地获得它!


看来您可以依靠一些很少的东西,但是感谢您指出我不知道并且确实更容易理解的功能。:)
Daren 2013年

是!正如@Svetoslav Tsolov所说,这取决于很多事情。我只想指出Arrays.copyOf
Philipp Sander

2
copyOf不能总是替换arraycopy,但是它适用于此用例。
Blaisorblade

1
注意:如果您正在查看性能,那么它不会像System.arraycopy()那样快,因为它需要内存分配。如果这是一个循环,那么重复分配将导致垃圾回收,这将严重打击性能。
威尔·卡德伍德

1
@PhilippSander只是为了检查我是否不是愚蠢的,我在游戏循环中向1MB数组的副本中添加了代码,几乎从未触发过GC。使用Array.copyOf(),我的DVM每秒调用5次GC,游戏变得非常缓慢。我认为可以肯定地说内存分配正在发生。
Will Calderwood

17

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

我作为嵌入式系统的Java开发人员已经工作了2年(性能是其中的重中之重),并且可以在所有可以使用System.arraycopy的地方使用它,我已经使用了大多数/看到它在现有代码中使用了。当性能成为问题时,总是优先于循环。如果性能不是大问题,那么我会顺其自然。更容易阅读。


数组大小和类型(基本vs继承)之类的东西似乎会影响性能。
达伦(Daren)2013年

2
是的,这本身并不是“本机性能”,这就是为什么我说我“主要”在可行的地方使用它(您会注意到它主要胜过循环复制)。我猜想原因是:当它是原始类型的小数组时,“通话成本”大于性能提升。出于相同的原因,使用JNI可能会降低性能-本机代码本身速度很快,但是从Java进程调用它-不会那么多。

较小的更正,Arrays.copy不是JNI,它是内在的。内在函数比JNI快得多。JIT编译器如何以及何时将其转换为内在函数取决于所使用的JVM /编译器。
Nitsan Wakart

1
Arrays.copy不存在,Arrays.copyOf是一个库函数。
Blaisorblade

12

我不依赖于猜测和可能过时的信息,而是使用 。实际上,Caliper附带了一些示例,其中包括一个CopyArrayBenchmark恰好可以测量该问题的示例!您要做的就是运行

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

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

综上所述:

  • for无论是短数组还是长数组,编写一个手动循环将每个元素复制到一个新实例化的数组中都是没有好处的。
  • Arrays.copyOf(array, array.length)并且array.clone()都一贯快速。这两种技术的性能几乎相同。您选择哪一个取决于口味。
  • System.arraycopy(src, 0, dest, 0, src.length)几乎和和一样快,但并不一致。(请参阅50000 秒的情况。)因此,以及调用的冗长程度,我建议是否需要精细控制将哪些元素复制到何处。Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

这是时序图:

复制长度为5的数组的时间 复制长度为500的数组的时间 复制长度为50000的数组的时间


3
关于int复制是否有些奇怪?数组复制在int上大规模运行会很慢,这似乎很奇怪。
whaleberg

6

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

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

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

来自这里的基准代码:-

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本机接口)来复制数组(或数组的一部分),因此速度非常快,您可以在此处确认


这段代码使用int []可以与String []一起尝试(用不同的值初始化:“ 1”,“ 2”等,因为它们是不可变的
Daren

1
JNI非常慢System.arraycopy不使用它。
Chai T. Rex

不,System.arraycopy不使用JNI,后者仅用于调用第三方库。相反,它是一个本地调用,这意味着VM中有一个本地实现。
spheenik '19

6

不可能Arrays.copyOfSystem.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;
}

4

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


3
我已经读过,对于非本机类型(任何创建的类,例如我的类),它可能效率不高……而对于小尺寸(以我为例),手动循环可能会更好……请关注一下?
达伦(Daren)2013年

实际上,System.arraycopy()会产生一些开销,因此对于小型数组(n =〜10),循环实际上会更快
RecursiveExceptionException
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.