【问题标题】:Why is System.arraycopy native in Java?为什么 System.arraycopy 在 Java 中是原生的?
【发布时间】:2011-02-15 20:39:43
【问题描述】:

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

当然是因为它更快。但是代码能够使用哪些原生技巧使其更快?

为什么不直接遍历原始数组并将每个指针复制到新数组 - 这肯定不是那么慢和麻烦?

【问题讨论】:

    标签: java native arrays


    【解决方案1】:

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

    【讨论】:

    • 其实只有arraycopy的部分子case可以使用memcpy/memmove实现。其他需要对复制的每个元素进行运行时类型检查。
    • @Stephen C,很有趣 - 为什么会这样?
    • @Péter Török - 考虑从填充有String 对象的Object[] 复制到String[]。见java.sun.com/javase/6/docs/api/java/lang/…的最后一段
    • Peter、Object[] 和 byte[] + char[] 是最常被复制的,它们都不需要显式类型检查。编译器足够聪明,除非需要,否则不会检查,实际上在 99.9% 的情况下它不是。有趣的部分是小型副本(小于缓存行)占主导地位,因此快速的小型副本的“memcpy”确实很重要。
    • @jainilvachhani memcpymemmove 都是 O(n),但是 f.e. simd 优化它们few times 更快,所以你可能会说它们是 O(n/x),其中 x 取决于这些函数中使用的优化
    【解决方案2】:

    它不能用 Java 编写。本机代码能够忽略或忽略 Object 数组和基元数组之间的区别。 Java 做不到,至少效率不高。

    而且它不能用单个memcpy() 编写,因为重叠数组需要语义。

    【讨论】:

    • 好的,那么memmove。虽然我认为这在这个问题的背景下没有太大区别。
    • 也不是 memmove(),请参阅@Stephen C 的 cmets 的另一个答案。
    • 已经看到了,因为这恰好是我自己的答案;-) 不过还是谢谢。
    • @Geek 重叠的数组。如果源和目标数组和相同且只有偏移量不同,则行为是仔细指定的,memcpy() 不符合。
    • 不能用Java写吗?不能编写一个通用方法来处理 Object 的子类,然后为每个基本类型编写一个吗?
    【解决方案3】:

    有几个原因:

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

      手写C实现的一些技巧和速度比较见这个链接(memcpy,但原理是一样的):查看这个Optimizing Memcpy improves speed

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

    【讨论】:

    • Java 代码可以得到优化。事实上,实际发生的是生成了比 C 更高效的机器代码。
    • 我同意有时 JITed 代码会更好地进行本地优化,因为它知道在哪个处理器上运行。然而,由于它是“及时的”,它将永远无法使用所有那些需要更长时间执行的非本地优化。此外,它永远无法匹配手工制作的 C 代码(这也可能会考虑处理器并部分否定 JIT 优势,无论是通过为特定处理器编译还是通过某种运行时检查)。
    • 我认为 Sun JIT 编译器团队会质疑其中的许多观点。例如,我相信 HotSpot 会进行全局优化以消除不必要的方法分派,并且 JIT 没有理由不能生成特定于处理器的代码。还有一点,JIT 编译器可以根据当前应用程序运行的执行行为进行分支优化。
    • @Stephen C - 关于分支优化的优点,尽管您也可以使用 C/C++ 编译器执行静态性能分析以达到类似的效果。我还认为热点有两种操作模式——桌面应用程序不会使用所有可用的优化来实现合理的启动时间,而服务器应用程序将被更积极地优化。总而言之,您获得了一些优势,但也失去了一些优势。
    • System.arrayCopy 不是使用 C 实现的,这会使这个答案无效
    【解决方案4】:

    当然,这取决于实现。

    HotSpot 会将其视为“内在”并在调用站点插入代码。那是机器代码,而不是缓慢的旧 C 代码。这也意味着方法签名的问题在很大程度上消失了。

    一个简单的复制循环非常简单,可以对其应用明显的优化。例如循环展开。究竟会发生什么再次取决于实现。

    【讨论】:

    • 这是一个非常不错的答案:),尤其是。提到内在函数。没有它们的简单迭代可能会更快,因为它通常由 JIT 展开
    【解决方案5】:

    在我自己的测试中,用于复制多维数组的 System.arraycopy() 比交错 for 循环快 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[]s 的 references),而嵌套的 for-loops 执行深层拷贝(float by float) .对fooCpy[i][j] 的更改将使用System.arraycopy 反映在foo 中,但不会使用嵌套的for 循环。
    猜你喜欢
    • 2015-01-29
    • 2022-01-20
    • 1970-01-01
    • 2010-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-23
    相关资源
    最近更新 更多