【问题标题】:java interface method inliningjava接口方法内联
【发布时间】:2019-02-10 03:53:20
【问题描述】:

我对来自 c++ 世界的 java 很陌生。 我正在运行一些服务器代码,该代码运行一个方法 foo() ,该方法每秒被调用几百万次。这是对延迟敏感的代码,并且该方法在分析器中也显示为进程消耗了总 CPU 使用量的 20%。

int foo_old() {
     if (Float.isNan(this.x)) { // shows up in profiling
        res = do some computation; // some floating point comparison, doesn't show up in profiling;
        return res;
     } else {
        // Happens 99% of the time;
        res = do something else; // some floating point comparison, doesn't show up in profiling;
        return res;
     }
}

有没有一种简单的方法可以测试我的方法 foo 是否会被内联?我可以从正在运行的服务器中的探查器堆栈跟踪信息中知道吗?

我通过简化方法 foo() 尝试了一些优化。基本上在 foo 中有一个 float.isNan 检查,它也显示在 profiler 上,惊讶地发现 nan 检查速度较慢。与其他一些布尔运算(小于、大于浮点比较)相比更快。

我尝试的一种方法是删除 nan check ,即因为我在编译期间知道对象是否需要 nan check ,所以我尝试存储一个功能接口(成员变量)并分配这个功能接口 foo_old (它具有 nan check )或 foo_optimized(不做 nan 检查)基于创建对象时已知的对象属性(在对象的构造函数中,我为此接口分配了正确的方法引用。

class A {
  final FuncIf test; // Functional interface with same signature as foo_old, foo_new

  public A(bool optimize) {
      test = optimize ? this::foo_optimized : this::foo_old;
  }

  // same as the original foo mentioned above
  int foo_old() {
    ...
   }

  // No nan check
  int foo_optimized() {
        res = do some computation; 
        return res;
  }
}

现在,当我创建对象时,我在编译时间/对象构造时间知道要使用哪个版本的 foo。所以我将接口变量分配给正确版本的 foo。部署后,我观察到延迟实际上增加了

是不是因为 foo 之前是直接方法调用,一旦我使用接口引用,调度虚拟 foo 的额外间接是我在延迟中看到的开销(接口方法调用的开销远大于 Nan 检查本身??) ? jvm编译器不能内联这个接口方法吗?

【问题讨论】:

  • 上面的解决方案一点用处都没有。关于延迟增加的原因或我可以尝试哪些其他方法来进一步了解 jvm/jit 正在做什么的任何见解都会更有益。
  • 给定主题不是解决方案,它描述了它的行为方式。所以你可能会理解这些限制。至少它说没有编译器或运行时会将非最终方法设为内联。它说所有优化都将由编译器和运行时完成(取决于编译器和我们使用的 JRE)。使用 final 方法,静态方法会加速你的应用程序。
  • res = do some computation; 提取到方法中可能会减小foo_old 的大小并使foo_old 的内联成为可能。然后 JVM 可能能够自行消除 isNaN 检查。 +++ 我看不出使用接口有什么帮助(但我可能错了)。
  • @maaartinus :做一些计算是非常简单的 1-2 浮点比较。所以我不认为提取对内联有任何影响,因为代码更小。代码已经太小了。第二个 foo_old 没有变小,它是一样的, foo_optimized 没有 nan 检查。你能详细说明为什么 jvm 可以自己摆脱 nan check 吗?我没有看到 jvm 能够做到这一点的任何方式,因为它取决于运行时数据(我看不到 jvm 运行时可以通过代码分析甚至使用运行时信息来检测或确定该值的方式)。

标签: java performance interface profiling virtual-functions


【解决方案1】:

唯一受过教育的猜测是测量它的bytecode。为此使用javap。基本上JVM 有两个编译器C1C2;两者都可以内联该方法。

JVM 在内联时关心三个参数(嗯,这些是我知道的,我也知道还有很多):

-XX:MaxInlineSize (35 by default)
-XX:FreqInlineSize (325 by default)
-XX:MinInliningThreshold (250 by default)

如果你的方法调用少于MinInliningThreshold (250),它遵循MaxInlineSize 规则,这意味着如果它小于35 字节,它将被内联。如果它被调用的更多,它会服从FreqInlineSize,这是 325 个字节(更多)。

您还可以通过一些参数打印是否内联的内容:

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

运行这些命令后,您会看到如下消息:

  callee is too large

这是由C1 打印的,它告诉您该编译方法超出了MaxInlineSize。或者:

  too big

超过MaxInlineSize 时由C2 编译器打印。或者:

  hot method too big

超过FreqInlineSize 时由C2 打印。

【讨论】:

  • 接口方法可以内联吗?还是需要额外的间接性?我持有对功能接口的引用,这似乎增加了延迟
  • @user179156 你的foo_optimized 捕捉到什么了吗?
  • 不,它适用于一些成员变量。因为它是一个类方法,我隐含地假设它捕获了“this”引用?
  • @user179156 它没有,除非需要
  • @user179156 当你说成员变量时......这意味着它确实适用于 lambda 之外的“某些东西”,对吗?例如x->x +y.getData() 其中y 在lamba 之外意味着捕获它
猜你喜欢
  • 1970-01-01
  • 2021-04-04
  • 2012-05-22
  • 1970-01-01
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-17
相关资源
最近更新 更多