jvm如何优化循环代码?

Sam*_*Sam 6 java string algorithm optimization jvm

有一种方法可以从文本中搜索子串(使用强力算法,请忽略空指针)

public static int forceSearch(String text, String pattern) {
    int patternLength = pattern.length();
    int textLength = text.length();

    for (int i = 0, n = textLength - patternLength; i <= n; i++) {
        int j = 0;
        for (; j < patternLength && text.charAt(i + j) == pattern.charAt(j); j++) {
            ;
        }
        if (j == patternLength) {
            return i;
        }
    }
    return -1;
}
Run Code Online (Sandbox Code Playgroud)

奇怪!使用相同的算法,但以下代码更快!

public static int forceSearch(String text, String pattern) {
    int patternLength = pattern.length();
    int textLength = text.length();

    char first = pattern.charAt(0);
    for (int i = 0, n = textLength - patternLength; i <= n; i++) {
        if (text.charAt(i) != first) {
            while (++i <= n && text.charAt(i) != first)
                ;
        }

        int j = 0;
        for (; j < patternLength && text.charAt(i + j) == pattern.charAt(j); j++) {
            ;
        }
        if (j == patternLength) {
            return i;
        }
    }
    return -1;
}
Run Code Online (Sandbox Code Playgroud)

如果我用jvm运行它,我发现第二个代码明显比第一个快.但是,当我在c中运行并运行时,这两个函数几乎同时进行.所以我认为原因是jvm优化循环代码

if (text.charAt(i) != first) {
    while (++i <= max && text.charAt(i) != first)
        ;
}
Run Code Online (Sandbox Code Playgroud)

我对吗?如果我是对的,我们应该如何使用jvm优化策略来优化我们的代码?

希望有人帮忙,谢谢你:)

Mik*_*bel 1

如果您确实想弄清楚这一点,您可能需要指示 JVM 打印程序集。根据我的经验,对循环的微小调整可能会导致令人惊讶的性能差异。但这不一定是由于循环本身的优化造成的。

有很多因素会影响代码的 JIT 编译方式。例如,调整方法的大小可能会影响您的内联树,这可能意味着更好或更差的性能,具体取决于您的调用堆栈的外观。如果方法在调用堆栈中进一步内联,则可能会阻止嵌套调用站点内联到同一帧中。如果这些嵌套调用站点特别“热门”,则增加的调用开销可能会很大。我并不是说这就是原因;我并不是说这是原因。我只是指出,有许多阈值控制 JIT 如何安排代码,并且性能差异的原因并不总是显而易见的。

使用 JMH 进行基准测试的一大好处是,您可以通过显式设置内联边界来减少此类更改的影响。但您可以-XX:CompileCommand手动使用来达到相同的效果。

当然,还有其他因素,例如缓存友好性,需要更直观的分析。鉴于您的基准测试可能没有特别深的调用树,我倾向于倾向于缓存行为作为更可能的解释。我猜你的第二个版本性能更好,因为你的比较数( 的第一个块pattern)通常位于 L1 缓存中,而你的第一个版本会导致更多的缓存搅动。如果您的输入很长(听起来很长),那么这就是一个可能的解释。如果不是,原因可能更微妙,例如,您的第一个版本可能会“欺骗”CPU 采用更积极的缓存预取,但实际上会损害性能(至少对于您正在基准测试的输入而言)。无论如何,如果要解释缓存行为,那么我想知道为什么您在 C 版本中没有看到类似的差异。您使用哪些优化标志来编译 C 版本?

消除死代码也可能是一个因素。我必须看看您的输入是什么,但您的手动优化版本可能会导致某些指令块在仪器解释阶段永远不会被命中,从而导致 JIT 将它们从最终汇编中排除。

我重申:如果您想弄清楚这一点,您将需要强制 JIT 转储每个版本的程序集(并与 C 版本进行比较)。