是什么导致 JNI 调用变慢?

发布于 2024-12-08 23:52:27 字数 107 浏览 0 评论 0原文

我知道在 Java 中进行 JNI 调用时“跨越边界”很慢。

但是我想知道是什么导致速度变慢? 当进行 JNI 调用时,底层 jvm 实现做了什么,导致速度如此之慢?

I know that 'crossing boundaries' when making a JNI call in Java is slow.

However I want to know what is it that makes it slow?
What does the underlying jvm implementation do when making a JNI call that makes it so slow?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

◇流星雨 2024-12-15 23:52:27

首先,值得注意的是,我们所说的“慢”是指可能需要数十纳秒的时间。对于简单的本机方法,2010 年,我在 Windows 桌面上测得平均调用时间为 40 ns,在 Mac 桌面上为 11 ns。除非您拨打很多电话,否则您不会注意到。

也就是说,调用本机方法可能比调用普通的 Java 方法。原因包括:

  • JVM 不会内联本机方法。它们也不会针对该特定机器进行即时编译——它们已经编译好了。
  • 可以复制 Java 数组以在本机代码中进行访问,然后再复制回来。成本可以与阵列的大小成线性关系。我测量了在 Windows 桌面上 JNI 复制 100,000 个数组的平均时间约为 75 微秒,在 Mac 上为 82 微秒。幸运的是,可以通过 GetPrimitiveArrayCritical 获得直接访问NewDirectByteBuffer
  • 如果该方法传递一个对象,或者需要进行回调,则本机方法可能会自行调用 JVM。从本机代码访问 Java 字段、方法和类型需要类似于反射的东西。签名在字符串中指定并从 JVM 查询。这既慢又容易出错。
  • Java 字符串是对象,有长度并且经过编码。访问或创建字符串可能需要 O(n) 副本。

一些可能已过时的附加讨论可以在 Steve Wilson 和 Jeff Kesselman 于 2000 年撰写的“Java(tm) Platform Performance: Strategies and Tactics”中的“9.2:检查 JNI 成本”部分中找到。大约是这个的三分之一页面,由@Philip 在下面的评论中提供。

2009 年 IBM DeveloperWorks 论文“使用 Java 本机接口的最佳实践” 提供了一些关于避免 JNI 性能陷阱的建议。

First, it's worth noting that by "slow," we're talking about something that can take tens of nanoseconds. For trivial native methods, in 2010 I measured calls at an average 40 ns on my Windows desktop, and 11 ns on my Mac desktop. Unless you're making many calls, you're not going to notice.

That said, calling a native method can be slower than making a normal Java method call. Causes include:

  • Native methods will not be inlined by the JVM. Nor will they be just-in-time compiled for this specific machine -- they're already compiled.
  • A Java array may be copied for access in native code, and later copied back. The cost can be linear in the size of the array. I measured JNI copying of a 100,000 array to average about 75 microseconds on my Windows desktop, and 82 microseconds on Mac. Fortunately, direct access may be obtained via GetPrimitiveArrayCritical or NewDirectByteBuffer.
  • If the method is passed an object, or needs to make a callback, then the native method will likely be making its own calls to the JVM. Accessing Java fields, methods and types from the native code requires something similar to reflection. Signatures are specified in strings and queried from the JVM. This is both slow and error-prone.
  • Java Strings are objects, have length and are encoded. Accessing or creating a string may require an O(n) copy.

Some additional discussion, possibly dated, can be found in "Java(tm) Platform Performance: Strategies and Tactics", 2000, by Steve Wilson and Jeff Kesselman, in section "9.2: Examining JNI costs". It's about a third of the way down this page, provided in the comment by @Philip below.

The 2009 IBM developerWorks paper "Best practices for using the Java Native Interface" provides some suggestions on avoiding performance pitfalls with JNI.

挽心 2024-12-15 23:52:27

值得一提的是,并非所有标有 native 的 Java 方法都是“慢”的。其中一些是内在函数,这使得它们速度极快。要检查哪些是内在的,哪些不是,您可以在 vmSymbols.hpp

It is worth mentioning that not all Java methods marked with native are "slow". Some of them are intrinsics that makes them extremely fast. To check which ones are intrinsic and which ones are not, you can look for do_intrinsic at vmSymbols.hpp.

一页 2024-12-15 23:52:27

基本上,JVM 解释性地为每个 JNI 调用构造 C 参数,并且代码没有优化。

中概述了更多详细信息本文

如果您对 JNI 与本机代码的基准测试感兴趣此项目有运行基准测试的代码。

Basically the JVM interpretively constructs the C parameters for each JNI call and the code is not optimized.

There are many more details outlined in this paper

If you are interested in benchmarking JNI vs native code this project has code for running benchmarks.

欢烬 2024-12-15 23:52:27

说到JNI,有两个方向:java调用C++,C++调用java。 Java 通过“native”关键字调用 C++(或 C)非常快,大约 50 个时钟周期。然而,C++调用Java有点慢。我们进行了大量的 Java/C++ 集成,我的经验法则是每次调用 1000 个时钟周期,因此您每秒可以获得大约 200 万次调用。我无法回答您“为什么这么慢”的实际问题,但我大胆猜测,必须做大量工作才能使用可变参数将参数从本机 C++ 堆栈传输到 Java 堆栈,验证任何一致性需要,反之亦然。

但是,还要记住,一旦从 C++ 调用 Java 方法,如果该方法返回复杂的数据结构,则还需要对结果的所有访问进行 JNI 调用。这同样适用于将复杂的 C++ 结构转换为 Java。例如,我们在实践中发现,序列化 C++ std::map的速度要快得多。转换为 JSON,通过 JNI 传递字符串,并让 Java 将其反序列化为 Map(假设您希望将整个映射转换为 Java)。

When talking about JNI, there are two directions: java calling C++, and C++ calling java. Java calling C++ (or C) via the "native" keyword is very fast, around 50 clock cycles. However, C++ calling Java is somewhat slow. We do a great deal of Java/C++ integration, and my rule of thumb is 1000 clock cycles per call, so you can get around 2M calls/second. I cannot answer your actual question of "why is it slow", but I'll hazard a guess that a lot of work has to be done to transfer arguments from the native C++ stack using varargs, onto the Java stack, validate whatever conformance is needed, and vice-versa on the return value.

However, also remember that once you make a call into a Java method from C++, if that method returns a complex data structure, you'll need to make JNI calls for all accesses into the result, as well. The same applies for converting complex C++ structure to Java. We've found in practice for example that it is much faster to serialize a C++ std::map<string,string> to JSON, hand the string across JNI, and have Java deserialize it into a Map<String,String>, assuming you want the entire map converted to Java.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文