【问题标题】:What makes JNI calls slow?是什么让 JNI 调用变慢?
【发布时间】:2011-10-08 18:46:22
【问题描述】:

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

但是我想知道是什么让它变慢了? 当 JNI 调用变得如此缓慢时,底层 jvm 实现会做什么?

【问题讨论】:

  • (+1) 好问题。当我们讨论这个主题时,我想鼓励任何做过实际基准测试的人发布他们的发现。
  • 一个JNI调用需要把传入的Java对象转换成C(例如)可以理解的东西;与返回值相同。类型转换和调用堆栈编组是其中很大一部分。
  • 戴夫,我了解并听说过。但是究竟是怎样的转换呢?那是什么东西?我正在寻找详细信息。
  • 使用直接 ByteBuffers 在 Java 和 C 之间传递数据可以导致相对较低的开销。
  • 调用需要适当的 C 堆栈帧,推送所有有用的 CPU 寄存器(并将它们弹回),调用需要隔离,并且它还阻止了许多优化,如内联。此外,线程必须离开执行堆栈锁(例如,允许偏向锁在本机代码中工作),然后将其取回。

标签: java performance java-native-interface


【解决方案1】:

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

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

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

在“Java(tm) Platform Performance: Strategies and Tactics”,2000 年,Steve Wilson 和 Jeff Kesselman 的“9.2:检查 JNI 成本”部分中可以找到一些额外的讨论,可能已经过时了。 @Philip 在下面的评论中提供了 this page 的大约三分之一。

2009 年 IBM developerWorks 论文"Best practices for using the Java Native Interface" 提供了一些关于避免 JNI 性能缺陷的建议。

【讨论】:

  • This answer 声称,一些本机代码可以被 JVM 内联。
  • 该答案指出,一些标准本机代码内联​​在 JVM 中,而不是使用 JNI。上面,“本机方法”是指通过 JNI 实现的用户定义本机方法的一般情况。感谢指向 sun.misc.Unsafe 的指针。
  • 我不想声称这种方法可以用于每个 JNI 调用。但是知道在纯字节码和纯 JNI 代码之间存在 一些中间地带,这并没有什么坏处。也许这会影响一些设计决策。也许这种机制在未来会被推广。
  • @A.H,你误用了 JNI 的内在函数。它们完全不同。 sun.misc.Unsafe 和很多其他的东西,比如 System.currentTimeMillis/nanoTime,都是由 JVM 通过“魔术”处理的。它们不是 JNI,而且它们根本没有适当的 .c/.h 文件,本身就暴露了 JVM impl。除非您正在编写/破解 JVM,否则无法遵循该方法。
  • this java.sun.com document”目前已损坏--here 是一个工作链接。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    this paper 中列出了更多详细信息

    如果您对 JNI 与本机代码的基准测试感兴趣,this project 有运行基准测试的代码。

    【讨论】:

    • 您链接到的论文似乎更像是一份性能基准论文,而不是描述 JNI 内部工作原理的论文。
    • @pdeva 不幸的是,我发现的其他资源链接到 java.sun.com,并且这些链接自 Oracle 收购以来一直没有更新。我正在寻找有关 JNI 内部结构的更多详细信息。
    • 这篇论文是关于 Java 1.3 - 很久以前的。那个时代的问题还适用于 Java 7 吗?
    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 2011-05-21
      • 2022-01-08
      • 2010-11-03
      • 1970-01-01
      • 2011-11-14
      • 1970-01-01
      • 2019-11-07
      • 2020-04-09
      • 2016-01-13
      相关资源
      最近更新 更多