Java JNI调用的开销

13

可能是重复问题:
JNI调用为什么慢?

首先,让我说这个问题更多的是出于好奇心而不是真正的必要性。

我很想知道在Java中使用JNI调用的开销是多少,比如使用System.arraycopy和使用for循环分配数组并复制元素之间的差别。

如果开销很大,那么可能会有一个大致的元素“魔数”,在此之前仅使用for循环就可以弥补,而不是使用System调用。并且,究竟是什么导致了这种开销的系统调用?我猜测栈必须推到调用的上下文中,这可能需要一段时间,但我找不到有关整个过程的好解释。

让我澄清我的问题:

我知道在Java中使用arraycopy是复制数组的最快方法。

也就是说,假设我正在使用它来复制仅包含一个元素的数组。由于我正在调用底层操作系统来执行此操作,所以该调用中肯定会有开销。我对知道这种开销以及调用过程中发生的事情感兴趣。

如果使用arraycopy使您误解了我的问题的目的,我很抱歉。我想知道JNI调用的开销以及实际调用中涉及的内容。


1
请注意,“System”调用是一个有些混淆的术语,因为它可能会被误解为系统调用 - Fred Foo
顺便提一下,在 https://dev59.com/BHE85IYBdhLWcg3wdDIM 中有大量信息。 - NPE
2
已删除我的回答,我认为这会对你有所帮助:https://dev59.com/EWsz5IYBdhLWcg3wuKa6 - Aviram Segal
4个回答

9

由于我需要调用底层操作系统来执行此操作...

您说得对,系统调用非常昂贵。但是,在System.arraycopy()中的System有点不准确。这里没有涉及任何系统调用。

...这个调用肯定会有开销。我想知道这个开销是多少,以及在调用过程中会发生什么。

当您查看System.arraycopy()的定义时,它被声明为native。这意味着该方法是用C++实现的。如果您愿意,可以查看JDK源代码,并找到C++函数。在OpenJDK 7中,它称为JVM_ArrayCopy(),位于hotspot/src/share/vm/prims/jvm.cpp中。实现非常复杂,但本质上是一个memcpy()

如果arraycopy()被用作普通的本地函数,那么调用它会有开销。由参数检查等引起的额外开销也会进一步增加。

然而,JIT编译器很可能知道System.arraycopy()。这意味着,编译器知道如何生成特殊制作的机器码来执行数组复制,而不是调用C++函数。我不知道其他JVM是否有这样的对System.arraycopy()的“内在”支持,但HotSpot确实有。

假设我正在使用它来复制一个仅包含一个元素的数组

如果您的数组很小,您可能能够通过手工循环击败System.arraycopy()。如果大小在编译时已知,则可以展开循环,从而达到更好的效果。然而,除了最狭窄的情况外,所有这些都不是真正相关的。


+1 内联本地调用的观点是最重要的贡献。 - Marko Topolnik
你的回答以及指向这个链接的评论:https://dev59.com/EWsz5IYBdhLWcg3wuKa6 对我最有帮助,所以我接受了。感谢你澄清了一些我没有表述清楚的术语。 - pcalcao
1
这是我见过的任何问题中最好的答案之一。 - Bohemian

2
看一下java.util.Arrays.copyOf的实现,例如:
public static byte[] copyOf(byte[] original, int newLength) {
    byte[] copy = new byte[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

他们使用System.arraycopy是因为这是最快的方式。
如果您指的是在Java中调用本地方法是否昂贵,请查看http://www.javamex.com/tutorials/jni/overhead.shtml 更新问题非常有趣,所以我做了一些测试。
        long t0 = System.currentTimeMillis();
        byte[] a = new byte[100];
        byte[] b = new byte[100];
        for(int i = 0; i < 10000000; i++) {
//            for(int j = 0; j < a.length; j++) {
//                a[j] = b[j];
//            }
            System.arraycopy(b, 0, a, 0, a.length);
        }
        System.out.println(System.currentTimeMillis() - t0);

它表明,在非常短的数组(小于10)上,System.arraycopy 可能会更慢,很可能是因为它是本地方法,但在更大的数组上,它不再重要,System.arraycopy 更快。

谢谢,已经点赞了你的链接,那确实是我在寻找的。 - pcalcao
我已经添加了一些测试,请查看我的更新。 - Evgeniy Dorofeev
“...很可能是因为它是本地的”,还有其他可能的解释,例如由于arraycopy()需要执行簿记来检查参数并选择正确的复制方式。 - Stephen C
没错,“因为它是本地的”并不是一个好的解释,我实际上是指人们谈论的所有关于jni开销的事情…… - Evgeniy Dorofeev

1
我对JNI调用的开销以及实际调用中涉及的内容很感兴趣。
System.arraycopy()方法相当复杂,JIT编译器不太可能将其内联(正如其他答案所建议的那样)。
另一方面,由于这是一个内在的本地方法,JIT编译器很可能使用优化的调用序列。换句话说,这很可能不是普通的JNI调用。

* - System.arraycopy 不是简单的内存复制。它必须测试其参数以避免读取或写入超出数组边界等情况。而且,在从一个对象数组复制到另一个对象数组的情况下,它可能需要检查每个复制对象的实际类型。所有这些都会增加远比内联合理的代码。


0

你的理解是错误的。System.arraycopy() 是 JVM 提供的超快本地实现。

没有“开销”,只有“优势”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接