是什么导致JNI调用变慢?

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

然而,我想知道什么是它使它变慢吗? 底层jvm实现在执行JNI调用时做了什么,导致调用如此缓慢?< / p >

48440 次浏览

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

这篇论文中列出了更多的细节

如果你对JNI与本机代码的基准测试感兴趣,这个项目有运行基准测试的代码。

首先,值得注意的是,“慢”;我们说的是几十纳秒就能搞定的事情。对于普通的本地方法,2010年我在Windows桌面上测量的调用平均为40纳秒,在Mac桌面上测量的调用平均为11纳秒。除非你正在进行许多调用,否则你不会注意到。

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

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

一些额外的讨论,可能有日期,可以在2000年Steve Wilson和Jeff Kesselman的“Java(tm)平台性能:策略和战术”中找到,在“9.2:检查JNI成本”章节中。它大约是这个页面的三分之一,在下面@Philip的评论中提供。

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

值得一提的是,并非所有标记为native的Java方法都是“慢”的。其中一些是intrinsic,这使得它们非常快。要检查哪些是内在的,哪些不是,你可以在vmSymbols.hpp处查找do_intrinsic

谈到JNI,有两个方向:java调用c++, c++调用java。Java调用c++(或C)通过“native"关键字非常快,大约50个时钟周期。然而,c++调用Java有点慢。我们做了大量的Java/ c++集成,我的经验法则是每次调用1000个时钟周期,所以你可以每秒进行2M次调用。我无法回答你“为什么很慢”的实际问题,但我大胆猜测,必须做大量工作,使用可变参数将参数从原生c++堆栈转移到Java堆栈,验证所需的任何一致性,反之亦然。

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