2009-02-13 87 views
8

我並不十分了解編譯器和JIT優化的內部知識,但我通常會嘗試使用「常識」來猜測什麼可以優化,哪些不能。所以我在寫一個簡單的單元測試方法今天:如何編寫(測試)不會被編譯器/ JIT優化的代碼?

@Test // [Test] in C# 
public void testDefaultConstructor() { 
    new MyObject(); 
} 

這種方法實際上是我所需要的。它檢查默認的構造函數是否存在並且運行時沒有異常。

但後來我開始考慮編譯器/ JIT優化的影響。編譯器/ JIT可以通過完全取消new MyObject();語句來優化此方法嗎?當然,它需要確定調用圖對其他對象沒有副作用,這是普通構造函數的典型情況,它只簡單地初始化對象的內部狀態。

我認爲只有JIT可以執行這樣的優化。這可能意味着它不是我應該擔心的事情,因爲測試方法只執行一次。我的假設是否正確?

儘管如此,我正在考慮一般的主題。當我想到如何防止這種方法被優化時,我想我可能assertTrue(new MyObject().toString() != null),但這非常依賴於toString()方法的實際實現,即使如此,JIT也可以確定toString()方法總是返回一個非null字符串(例如,如果實際上調用了Object.toString()),並因此優化整個分支。所以這種方式是行不通的。

我知道在C#中我可以使用[MethodImpl(MethodImplOptions.NoOptimization)],但這不是我真正想要的。我希望找到一種(語言無關)的方式,確保我的代碼的某些特定部分能夠按照我的預期實際運行,而JIT不會干擾此過程。

此外,是否有任何典型的優化案例,我應該知道創建我的單元測試?

非常感謝!

回答

4

不用擔心。不允許優化任何可以改變系統的內容(速度除外)。如果你新建了一個對象,代碼被調用,內存被分配,它必須工作。

如果您有它由IF(假),其中虛假的最後,它可以被優化掉了系統完全的,那麼它可以檢測,該方法不執行任何操作,並優化它的保護(在理論)。

編輯:順便說一下,也可以足夠聰明,以確定這種方法:

newIfTrue(boolean b) { 
    if(b) 
     new ThisClass(); 
} 

將永遠做什麼,如果b爲假,並最終弄清楚,在你的代碼B中的一個點始終是錯誤的,並且完全從該代碼編譯該例程。

這就是JIT可以做的東西,在任何非託管語言幾乎是不可能。

+0

謝謝。我想知道爲什麼JIT的行爲如此?如果一個對象分配是無用的(在某些情況下實際上可以通過靜態分析來確定),爲什麼JIT不會對它進行優化? – 2009-02-13 22:46:21

+0

雖然我現在可以想到一個角落案例,但我認爲這很少見。如果爲了確保有足夠的內存可用於某些其他對象(甚至確保不會發生分頁)而完成對象分配,則優化將使該假定失效。 – 2009-02-13 22:48:24

+0

需要將Java虛擬機(JVM)保持在與根據Java內存模型在JVM中執行程序代碼一致的狀態。在JIT可以證明代碼對可觀察程序狀態沒有影響的情況下,並不需要實際執行任何特定代碼或分配內存。 – 2013-05-20 20:18:38

5

我想如果你擔心它被優化掉了,你可能會做一些測試矯枉過正。

在靜態語言中,我傾向於將編譯器視爲測試。如果它通過編譯,那就意味着某些東西在那裏(如方法)。如果你沒有另外一個測試來執行你的默認構造函數(這將證明它不會拋出異常),你可能想要考慮爲什麼你要寫這個默認構造函數(YAGNI和所有這些)。

我知道有些人不同意我的看法,但是我覺得這種事情只是一些無用的理由,會讓你的測試數量膨脹,甚至通過TDD護目鏡觀察它。

+0

是,測試矯枉過正FTL。 – MichaelGG 2009-02-13 22:38:28

+0

我同意你的意見。當我想到它時(在閱讀你的答案之後),如果這個測試更重要的是隻是一個「編譯」測試,那麼必須有其他測試使用默認的構造函數。謝謝! – 2009-02-13 22:43:35

+0

在像Java這樣的語言中,編譯和執行環境可能差別很大。如果正在構建的類與測試代碼分開定位,那麼這樣的測試可能會有意義。特別有趣的將有一個自定義的類裝載器裝載上飛從某種形式的動態存儲的類 - 這個代碼將確保查找實際工作。 – 2013-05-20 20:21:35

0

爲什麼要重要?如果編譯器/ JIT可以靜態確定任何斷言都將被命中(這可能會導致副作用),那麼你很好。

+0

它還需要驗證構造函數是否存在,不會影響靜態程序狀態,並且不能拋出JVM隱式需要的異常,例如`NullPointerException`。 – 2013-05-20 20:16:28

2

想想這樣說:

讓我們假設編譯器能夠確定調用圖形沒有任何副作用(我不認爲這是可能的,我依稀記得一些關於P = NP從我的CS課程)。它會優化任何沒有副作用的方法。由於大多數測試沒有,也不應該有任何副作用,編譯器可以將它們全部優化。

1

看來,在C#中我可以這樣做:

[Test] 
public void testDefaultConstructor() { 
    GC.KeepAlive(new MyObject()); 
} 

AFAIU,該GC.KeepAlive方法將不會被JIT內聯,所以代碼將保證按預期運行。但是,我不知道Java中的類似構造。

0

每個I/O是一個副作用,所以你可以把

Object obj = new MyObject(); 
System.out.println(obj.toString()); 

和你的罰款。

2

的JIT僅允許執行不影響語言的語義保證操作。從理論上說,它可以刪除分配和調用到MyObject構造如果它可以保證調用沒有副作用,不能拋出異常(不包括OutOfMemoryError)。

換句話說,如果JIT優化召喚出你的測試,那麼你的測試將反正過去了。

PS:請注意,這也適用,因爲你正在做功能測試,而不是性能測試。在性能測試中,確保JIT不會優化您正在測量的操作非常重要,否則您的結果將變得毫無用處。