【问题标题】:JIT compiler optimization: second method call with same parameterJIT 编译器优化:第二个方法调用相同的参数
【发布时间】:2023-11-07 23:06:01
【问题描述】:

假设我有一个用于验证特定值的方法,例如一个方法isEven

public static boolean isEven(int evenSize) {
    return evenSize % 2 == 0;
}

我使用这种方法来验证外部输入(例如来自磁盘或来自用户)。但在那之后,我也在一个需要偶数值的方法中使用了这个方法:

public static String padToEven(int evenSize, String string) {
    if (!isEven(evenSize)) { // <-- duplication of isEven method
        throw new IllegalArgumentException("evenSize argument is not even");
    }

    if (string.length() >= evenSize) {
        return string;
    }

    StringBuilder sb = new StringBuilder(evenSize);
    sb.append(string);
    for (int i = string.length(); i < evenSize; i++) {
        sb.append('x');
    }
    return sb.toString();
}

所以基本上我们向padToEven() 提供已经验证的参数,并且使用相同的isEven 函数验证参数。 JIT 编译器(比如在 Java 版本 8 中)是否有可能找到第二个调用并将其优化掉?

您可以假设检查不依赖于动态值(即它对提供的参数值具有确定性)。除了日志语句等返回值之外,它也没有任何副作用。

【问题讨论】:

  • 显然对于isEven 没关系,但例如isPrime 操作上的差异可能很明显,尤其是在循环中使用时。
  • 有趣的问题!然而,我怀疑在一般情况下是否有可能安全地确定这种重用。
  • @BoristheSpider 是的,可能非常棘手。 JIT 编译器有很多未知数(副作用、状态),调用也可能用很多字节码分隔。
  • 这取决于您没有提到的一些事情,例如该值如何进入该函数以及这些时刻之间控制流的形状。例如,如果padToEven 在某处被调用为已经检查过isEven 的函数的后代,那么它比@987654330 更容易推理(但仍然是我不期望从JVM 获得的过程间分析级别) @ 调用读取值、检查值、将其放入某个静态字段的东西,然后调用涉及padToEven 的其他东西。它还取决于谓词是否可以由数字抽象域表示。

标签: java validation optimization parameters jit


【解决方案1】:

我认为 Java 根本不进行任何过程间分析。但是,该方法有可能被内联。当您内联所有内容时,就会有

if (evenSize % 2 != 0) {
    throw new IllegalArgumentException("evenSize argument is not even");
}
... some code not changing evenSize
if (evenSize % 2 != 0) {
    throw new IllegalArgumentException("evenSize argument is not even");
}

这是相当微不足道的优化。这种内联不是您可以依赖的,因为有一个内联限制会很快达到。

其他优化

OTOH,测试很简单,可能会优化到

public static boolean isEven(int evenSize) {
    return (evenSize & 1) == 0;
}

它使用更快的操作。但这不是什么,我会关心(因为周围有很多其他代码,所以你不会有太多收获)。


我想,最好的优化是消除StringBuilder。什么???是的,说真的,char[] 可以:

char[] result = new char[evenSize];
for (int i = 0; i < string.length(); i++) {
    result[i] = string.charAt(i);
}
for (int i = string.length(); i < evenSize; i++) {
    result[i] = 'x';
}
return new String(result);

前段时间,我做了一些测量,表明它应该更快。这可能随着最近的 JIT 而改变。不重要的部分:

什么优化在这里有意义?

根本没有。除非你

  • 确实需要提高性能
  • 分析它并找出罪魁祸首
  • 准备好花相当长的时间进行基准测试和分析

只是不要这样做。幸运的是,JIT 已针对具有简短方法的干净代码进行了优化。它无法改进您的算法和数据结构,因此您可以在真正需要时进行优化。微优化的回报要少得多。

你上面的代码没问题,别碰它。

【讨论】:

  • 太好了,谢谢!请注意,isEven 方法只是通用验证方法的一个示例。我唯一询问的是该方法调用的重复。因此,直到“OTOH”的所有内容都是主题。其余的是关于代码其他部分的有趣讨论,这很好,但没有回答问题。所以基本上答案是:不会被优化,除非内联。
  • @MaartenBodewes 我已经意识到写太多 w.r.t.这个问题,但我按照我的想法做了,它可能有用。 ;)
  • 因为我通常也会提供与加密相关的答案的安全建议,所以我几乎不能抱怨 :) 我将答案分成 3 部分,而不是 2 部分,希望你不介意。
  • 当然,Java “进行过程间分析”。这就是首先进行内联,然后进行所有其他优化的全部意义所在。所以这只是命名的问题。
  • 我怀疑手动处理char 数组会带来什么好处,即使是使用较旧的 JVM 也是如此。此外,在手动处理数组时,没有理由重复调用charAt 的第一个循环应该比单个string.getChars(0, string.length(), result, 0) 调用更快......
最近更新 更多