2013-03-03 27 views
23

我正在使用我正在構建的一些搜索代碼中Levenshtein算法的優化版本。我有功能單元測試來驗證算法是否返回了正確的結果,但在這種情況下,算法的性能也非常重要。如何在C#中測試性能優化?

我在爲項目添加一些測試覆蓋,以便如果任何未來的修改影響優化,它們將顯示爲失敗的測試 - 因爲算法是確定性的並且針對已知的測試數據運行,這可能是詳細如計數對於給定的一組測試輸入執行的指令的數量。換句話說,我不想用定時器來衡量算法的性能 - 我有興趣實際測試算法的內部行爲而不是輸出。

任何想法如何在C#/ .net 4中處理這個問題?

編輯:我不想只使用掛鐘時間的原因是,它會隨着CPU負載和測試控制之外的其他因素而變化。例如,這可能會導致構建服務器處於負載下時測試失敗。 作爲部署系統的一部分進行掛鐘監控。

編輯2:想想這樣...當性能是關鍵要求時,如何應用red-> green-> refactor?

+1

執行的指令數量不一定等於性能。爲什麼在這裏不是時鐘的關鍵指標呢? – 2013-03-03 01:13:12

+0

同意 - 但如果爲了響應特定的一組輸入而執行的指令數量因代碼修改而發生顯着變化,我想知道這一點。這就是我要找的東西。 – 2013-03-03 01:14:35

+2

我不會使用單元測試來驗證。如果開發人員正在改變工作方式,完成高度優化的代碼,那麼他們應該確切地知道他們在做什麼。這段代碼不太可能需要進行'調整'嗎?那爲什麼要麻煩?性能是一個非功能性特徵。 – 2013-03-03 01:14:45

回答

30

我打算回答你的問題的第三部分,因爲我已經多次取得了一些成功。

當性能是 關鍵要求時,您將如何應用red-> green-> refactor?

  1. 寫釘扎測試,以趕上回歸,對於計劃更改,並且可能會減慢爲你改變的結果的其他方法是什麼。
  2. 編寫失敗的性能測試。
  3. 提高性能,頻繁運行所有測試。
  4. 更新您的釘扎測試,以更貼近的性能。

寫牽制測試

創建的helper方法像這樣要固定的時間。

private TimeSpan Time(Action toTime) 
{ 
    var timer = Stopwatch.StartNew(); 
    toTime(); 
    timer.Stop(); 
    return timer.Elapsed; 
} 

然後寫一個測試,斷言你的方法沒有任何時間:

[Test] 
public void FooPerformance_Pin() 
{ 
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0)); 
} 

當它失敗(在失敗的消息所經過的實際時間),更新的東西比略多的時間實際時間。重新運行,它會通過。對其他可能影響您的更改的功能重複執行此操作,最終得到類似的結果。

[Test] 
public void FooPerformance_Pin() 
{ 
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0.8)); 
} 
[Test] 
public void BarPerformance_Pin() 
{ 
    Assert.That(Time(()=>fooer.Bar()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(6)); 
} 

寫一個失敗的性能測試

我喜歡稱這種測試的「引誘測試」。這只是釘扎測試的第一步。

[Test] 
public void FooPerformance_Bait() 
{ 
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0)); 
} 

現在,在性能改進上工作。在每次試驗性改進後運行所有測試(固定和誘餌)。如果成功,您會看到誘餌測試的失敗輸出中的時間會減少,並且您的任何固定測試都不會失敗。

當您對改進感到滿意時,請更新您更改的代碼的固定測試,並刪除誘餌測試。

你現在對這些測試做什麼?

最簡單的做法是用Explicit屬性標記這些測試,並在下次希望檢查性能時保留它們。

在工作範圍的另一面,在CI中創建一個合理控制良好的子系統來運行這些測試是監控績效迴歸的一個非常好的方法。根據我的經驗,很多人擔心他們「因其他的CPU負載而隨機失敗」,而不是真正的失敗。這種努力的成功與否取決於團隊文化,而不是您對環境的控制能力。

+0

我不確定我是否理解你的最終評論。毫無疑問,它具有良好的控制能力,可以確保性能測試在特定的機器上運行,而且一次只能執行一次,並且儘可能多地禁用後臺進程,從而實現**所有**。並且也可能確保例如在運行開始之前緩存被刷新。如果你不做大部分/全部的上述工作,我不會看到你如何做有意義的業績迴歸(除非你設置了一個非常寬鬆的門檻)。 – 2013-03-03 10:56:11

+0

我認爲你可能是對的 - 特別是關於需要一個控制良好的CI子系統來監控績效迴歸。我認爲一個專門的TeamCity構建代理可能是一個很好的方法 - 我們已經做過類似的事情,比如之前的長時間運行的Selenium測試。感謝這樣一個全面的答案。 – 2013-03-03 11:14:13

+0

@OliCharlesworth,你有沒有試過像CI這樣的性能測試?我第一次做這件事的時候出現了很多推動力。事實是,雖然它確實需要建立起來,但讓它們穩定並不是很多工作。在我們嘗試之前,與反對者爭論還有很多工作要做。 YMMV,大多數這些測試都是以秒爲單位來衡量的,而不是毫秒。我期望的是,非常快的性能測試對於外部條件會更脆弱。 – tallseth 2013-03-04 00:24:22