簡體   English   中英

jvm如何優化循環代碼?

[英]How does jvm optimize loop code?

有一種方法可以從文本中搜索子串(使用強力算法,請忽略空指針)

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優化策略來優化我們的代碼?

希望有人幫忙,謝謝你:)

這個if語句簡化了很多工作(特別是在輸入字符串末尾找到模式時)。

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

在第一個版本中,您必須在比較第一個字符之前檢查每個i的j < patternLength

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

但奇怪的是,我認為對於小輸入它並沒有太大的不同。

你可以分享用於基准測試的項目的長度嗎?

如果你真的想深究這一點,你可能需要指示JVM打印程序集。 根據我的經驗,對循環的微小調整可能會導致令人驚訝的性能差異。 但這並不一定是由於循環本身的優化。

有很多因素會影響您的代碼如何編譯JIT。 例如,調整方法的大小會影響內聯樹,這可能意味着更好或更差的性能,具體取決於調用堆棧的外觀。 如果方法在調用堆棧中進一步內聯,則可以防止嵌套的調用站點內聯到同一幀中。 如果這些嵌套的呼叫站點特別“熱”,則增加的呼叫開銷可能很大。 我不是說這就是原因; 我只是指出有很多閾值可以控制JIT如何安排代碼,性能差異的原因並不總是很明顯。

將JMH用於基准測試的一個好處是,您可以通過顯式設置內聯邊界來減少此類更改的影響。 但您可以使用-XX:CompileCommand手動實現相同的效果。

當然,還有其他因素,如緩存友好性,需要更直觀的分析。 鑒於您的基准測試可能沒有特別深的調用樹,我傾向於傾向於緩存行為作為更可能的解釋。 我猜你的第二個版本表現更好,因為你的比較(第一個pattern塊)通常在你的L1緩存中,而你的第一個版本導致更多的緩存流失。 如果您的輸入很長(聽起來像是這樣),那么這可能是一種解釋。 如果沒有,原因可能會更加微妙,例如,您的第一個版本可能會“欺騙”CPU采用更積極的緩存預取,但實際上會損害性能(至少對於您正在進行基准測試的輸入)。 無論如何,如果要解釋緩存行為,那么我想知道為什么你沒有看到C版本中的類似差異。 您在編譯C版本時使用了哪些優化標志?

死代碼消除也可能是一個因素。 我必須看看你的輸入是什么,但你的手動優化版本可能會導致某些指令塊在儀表化解釋階段永遠不會被命中,導致JIT將它們排除在最終裝配之外。

我重申:如果你想深究這一點,你將要強制JIT轉儲每個版本的程序集(並與C版本進行比較)。

如果你在互聯網上搜索JVM編譯器優化,那么

“循環展開”或“循環展開”

應該跳出來。 基准測試再次變得棘手。 你會發現很多相同的答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM