jvm如何优化循环代码?

时间:2018-05-05 04:18:09

标签: 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;
}

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

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;
}

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

if (text.charAt(i) != first) {
    while (++i <= max && text.charAt(i) != first)
        ;
}

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

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

3 个答案:

答案 0 :(得分:1)

此if语句简化了大量工作(特别是在输入字符串末尾找到模式时。

   if (text.charAt(i) != first) {
        while (++i <= n && text.charAt(i) != first)
            ;
    }

在第一个版本中,在比较第一个字符之前,您必须为每个i检查j < patternLength

在第二个版本中,您不需要。

但奇怪的是,我认为对于小输入它并没有太大的不同。

你能分享用于基准测试的项目的长度吗?

答案 1 :(得分:1)

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

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

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

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

编译C版本的优化标志是什么

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

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

答案 2 :(得分:0)

如果您在互联网上搜索JVM编译器优化,

  

&#34;循环展开&#34;或&#34;循环展开&#34;

应该跳出来。基准测试再次变得棘手。你会发现大量的答案。