2011-02-11 31 views
12

我有興趣是否應該手動內聯一些在性能敏感算法中稱爲100k-100萬次的小方法。首先,我認爲,通過不內聯,我招致了一些開銷,因爲JVM將不得不查找是否要內聯這個方法(甚至不這樣做)。Java - 調用靜態方法vs手動內聯 - 性能開銷

但是,有一天,我用靜態方法調用了這個手動內聯代碼,並看到性能提升。這怎麼可能?這是否表明實際上沒有開銷,並且讓JVM以「其意志」內聯來實際提升性能?或者這很大程度上取決於平臺/架構?

(發生性能提升的例子是用靜態方法調用swap(int[] a, int i, int j)替換了數組交換(int t = a[i]; a[i] = a[j]; a[j] = t;)。另一個沒有性能差異的例子是當我將一個被稱爲1000000次的10-liner方法)

回答

11

我看過類似的東西。 「手動內聯」不一定更快,結果程序對於優化器來說可能太複雜以至於無法分析。

在你的例子中,讓我們做一些野生猜測。當你使用swap()方法時,JVM可能能夠分析方法體,並得出結論,由於i和j沒有改變,儘管有4個數組訪問,但只需要2個範圍檢查而不是4個。本地變量t不是必需的,JVM可以使用2個寄存器來完成這項工作,而不需要在堆棧上包含t的r/w。

後來,swap()的主體被內聯到調用方法中。那是在先前的優化之後,所以保存仍然存在。調用者方法體甚至有可能證明i和j總是在範圍內,所以剩餘的兩個範圍檢查也被丟棄。

現在在手動內聯版本中,優化器必須一次性分析整個程序,變量太多,操作太多,可能無法證明保存範圍檢查是安全的,或者消除局部變量t。在最壞的情況下,這個版本可能會花費6個內存訪問來完成交換,這是一個巨大的開銷。即使只有一個額外的內存讀取,它仍然是非常明顯的。

當然,我們沒有任何理由相信最好是手動「勾畫」,即提取小方法,並希望能夠幫助優化器。

-

我學到的是,忘記手動微優化。這並不是說我不關心微型性能的改進,並不是我總是相信JVM的優化。這是我絕對不知道該做什麼,比做得更好。所以我放棄了。

0

熱點JIT編譯器能夠內聯很多事情,特別是在-server模式下,儘管我不知道如何得到實際的性能提升。 (我的猜測是內聯是通過方法調用計數完成的,並且交換這兩個值的方法不會太頻繁地調用)。

順便說一下,如果它的性能真的很重要,那麼可以嘗試更換兩個int值。 (我不是說這會更快,但它可能是值得一平底船)

a[i] = a[i]^a[j]; 
a[j] = a[i]^a[j]; 
a[i] = a[i]^a[j]; 
+3

幾乎沒有任何現代建築的速度。 – Puppy 2011-02-11 14:34:03

1

然而,有一天,我換成這個手動內聯代碼的靜態方法調用,並看到了性能促進。這怎麼可能?

也許JVM分析器在一個地方(靜態方法)更容易看到瓶頸,而不是分開實施多次。

8

JVM可以非常有效地內聯小方法。將自己內聯的唯一好處是如果您可以刪除代碼,即通過內聯代碼來簡化它的功能。

JVM查找某些結構,並在識別出這些結構時進行一些「手動編碼」優化。通過使用交換方法,JVM可以識別結構並通過特定的優化以不同的方式進行優化。

您可能有興趣嘗試OpenJDK 7調試版本,該版本可以打印出生成的本機代碼。

2

對不起,我遲到的回覆,但我剛剛發現這個話題,它引起了我的注意。

在Java中開發時,嘗試編寫「簡單和愚蠢的」代碼。理由:

  1. 優化是在運行時進行的(因爲編譯本身是在運行時進行的)。因爲編譯器不會編譯你編寫的源代碼,而是編譯它的內部表示(幾個AST - > VM代碼 - > VM代碼 - - >本地二進制代碼轉換是在運行時由JVM編譯器和JVM解釋器執行)
  2. 當優化編譯器時,使用一些常用編程模式來決定優化哪些內容;所以幫他幫助你!寫一個私有的靜態(也許也最終)方法,它會立即它可以計算出:
    • 內嵌的方法
    • 其編譯爲本地代碼

如果該方法是手動內聯,它只是編譯器首先試圖理解的另一種方法的一部分,並且看看是否應該將其轉換爲二進制代碼,或者它是否必須稍微瞭解一下程序流程。另外,取決於該方法的作用,在運行時期間可能會有幾次重新調整JIT'> JVM僅在「預熱」之後才生成最佳二進制代碼......並且可能在JVM自動升溫之前程序結束了(因爲我預計最終的表現應該相當類似)。

結論:在C/C++中優化代碼是有意義的(因爲靜態編譯爲二進制文件),但是相同的優化通常不會對Java產生影響,其中編譯器JIT是字節碼,而不是您的源代碼。順便說一句,從我看到的javac甚至不打擾優化:)