性能測試
日常工作中對比函數間的快慢時,最直接的方法就是根據timer:tc/1
結果的時間來衡量,比如想知道lists:reverse/1
與直接使用自己寫的尾遞歸函數
誰更快?最直接的方法就是
-module(test).
-export([start/1]).
start(Len) ->
L = lists:seq(1, Len),
Max = 1000,
Time1 = benchmark(Max, fun() -> lists:reverse(L) end),
Time2 = benchmark(Max, fun() -> tail_reverse(L) end),
Time1/Time2.
benchmark(Max, Fun) -> benchmark(Max, Fun, 0).
benchmark(0, _Fun, Time) -> Time;
benchmark(Count, Fun, Acc) ->
{Time, _} = timer:tc(Fun),
benchmark(Count - 1, Fun, Time + Acc).
tail_reverse(L) -> tail_reverse(L, []).
tail_reverse([], Acc) -> Acc;
tail_reverse([X | Xs], Acc) -> tail_reverse(Xs, [X | Acc]).
在shell中直接運行
1> [begin test:start(Len) end||Len<-lists:seq(1,10000, 1000)].
[0.9605911330049262,0.9538599640933573,0.4008409250175193,
0.4468459717078502,0.42090136752676627,0.5073483137748491,
0.4653574844571975,0.47523496362271184,0.508740383302226,
0.5094403169886083]
可以看出,lists:reverse/1
比尾遞歸快,且列表長度越大時,lists:reverse/1
越優。
但是以上的結論建立在所有的測試條件完全一致的情況下。但達到完全一致是完全不可能的,因為在Erlang VM在測試過程中也可能在做其它的事(GC,其它調度工作). 即便再優化代碼,盡量保持VM不在干其它的事,但是還需要保證測試過程中操作系統條件一致,假如測試時使用的是筆記本電腦測試,你可能會在測試的過程中不經意的切換了一下web瀏覽器,看一下視頻,郵件,或者由於電量問題系統自動切換了CPU的頻次。
換句話來說,有太多擾亂性能測試結果的因素,其中有一些我們無法察覺或無法控制的,但它們可能不會發生或可能不重要,這些變數混雜在一起讓本來需要精確對比的結果變得難以衡量。
所以這種求平均數的方法在需要精確對比的性能測試中並沒有十足的說服力。Student't T-Test就是專用於解決這類問題的。
Student's T-Test
T-Test通過對比平均數(averages)和調和平均數(means),可以告訴你樣本間不同的程度有多大。它可以讓你知道這些差異是否是偶然發生的。舉個例子:
一家制葯公司可能想要測試一種新的抗癌葯物,以確定它是否可以提高預期壽命。在一項實驗中,總有一個對照組(給予安慰劑的組)。對照組可能顯示平均預期壽命為+5年,而服用新葯的組可能預期壽命為+6歲。似乎該葯物可能起作用。但這可能是由於僥幸。為了測試這一點,研究人員將使用T-Test檢驗來確定整個人群的結果是否可重復。
T-Test看起來幾乎與正態分布曲線相同,只是更短更胖。當您有小樣本時,使用t分布而不是正態分布。樣本量越大,t分布越接近正態分布。實際上,對於大於20的樣本大小(例如:更大的自由度),分布幾乎與正態分布完全相同。
如何一步一步手動計算配對T-Test。
0. 假如有以下兩個樣本。
-
使用X-Y得到差值。
-
把上一步得到的所有差值都相加。
-
計算第一步中X-Y的平方值。
-
把第三步中的所有平方值相加。
-
使用公式計算t-score。
ΣD: Sum of the differences (Sum of X-Y from Step 2)
ΣD2: Sum of the squared differences (from Step 4)
(ΣD)2: Sum of the differences (from Step 2), squared.
6. 樣本的個數-1得到自由度(the degrees of freedom) 11 -1 = 10。
7. 通過查t-table表得到p-value。使用第6步得到的自由度查表。我們使用95%的Confidence level, 所以對應的此樣本是 df=10 alpha level = 5% 查得t-value為2.228。
- 對比2.228和在第5步計算的-2.74, -2.74的絕對值大於2.228所以 p-value < 0.05。我們可以拒絕無效假設,即均值之間沒有區別。
eminstat的使用及結果分析
那么如何把T-Test運用到我們的性能測試中呢?
eminstat
就是使用T-Test方法來測試檢驗樣本間是否有差異的庫。它使用非常簡單,不過要完全看懂它的結果,必須要對T-Test有一定的了解。
下面我們還是以reverse的例子來詳細介紹一下eminstat
使用和結果分析。
rebar3新建庫並更新依賴。
rebar3 new lib bencherl
===> Writing bencherl/src/bencherl.erl
===> Writing bencherl/src/bencherl.app.src
===> Writing bencherl/rebar.config
===> Writing bencherl/.gitignore
===> Writing bencherl/LICENSE
===> Writing bencherl/README.md
cat rebar.config
{deps, [{eminstat, {git, "https://github.com/jlouis/eministat.git", {branch, master}}}]}.
rebar3 compile
編寫測試用例
cat ebench_reverse.erl
-module(ebench_reverse).
-export([t/0, t/2]).
-define(LOOP, 1000).
t() ->
t(1000, 40).
t(Len, Count) ->
[H | T] = datasets(Len, Count),
eministat:x(95.0, H, T).
lists_r(Items) -> lists_r(Items, ?LOOP).
lists_r(_, 0) -> ok;
lists_r(Items, K) ->
lists:reverse(Items),
lists_r(Items, K-1).
tail_r(Items) -> tail_r(Items, ?LOOP).
tail_r(_, 0) -> ok;
tail_r(Items, K) ->
tail_reverse(Items),
tail_r(Items, K-1).
datasets(Len, Count) ->
Items = lists:seq(1, Len),
[
eministat:s("lists:reverse", fun() -> lists_r(Items) end, Count),
eministat:s("tail_reverse", fun() -> tail_r(Items) end, Count)
].
tail_reverse(L) -> tail_reverse(L, []).
tail_reverse([], Acc) -> Acc;
tail_reverse([X | Xs], Acc) -> tail_reverse(Xs, [X | Acc]).
得到測試結果
rebar3 shell
1>ebench_reverse:t(3000, 30).
x lists:reverse
+ tail_reverse
+--------------------------------------------------------------------------+
|xxxxxxx x ++++ +|
|xxxxx x ++++ |
|xxxx +++ |
|xxx +++ |
|xx +++ |
| x +++ |
| x + |
| x + |
| x + |
| x + |
| x + |
| x + |
| + |
| + |
| + |
||MA_| |
| |___MA____| |
+--------------------------------------------------------------------------+
Dataset: x N=30 CI=95.0000
Statistic Value [ Bias] (Bootstrapped LB‥UB)
Min: 8295.00
1st Qu. 8494.00
Median: 8700.00
3rd Qu. 9467.00
Max: 1.14400e+4
Average: 9088.53 [ -3.07012] ( 8849.00 ‥ 9447.13)
Std. Dev: 831.129 [ -28.0026] ( 608.188 ‥ 1129.95)
Outliers: 0/1 = 1 (μ=9085.46, σ=803.126)
Outlier variance: 0.484430 (moderate)
------
Dataset: + N=30 CI=95.0000
Statistic Value [ Bias] (Bootstrapped LB‥UB)
Min: 2.67010e+4
1st Qu. 2.70370e+4
Median: 2.71440e+4
3rd Qu. 2.75240e+4
Max: 3.82770e+4
Average: 2.76068e+4 [ -3.36850] ( 2.72001e+4 ‥ 2.91141e+4)
Std. Dev: 2043.00 [ -372.223] ( 307.987 ‥ 4224.71)
Outliers: 0/1 = 1 (μ=2.76034e+4, σ=1670.78)
Outlier variance: 0.384784 (moderate)
Difference at 95.0% confidence
1.85183e+4 ± 806.172
203.754% ± 8.87022%
(Student's t, pooled s = 1559.59)
------
ok
我們對長度為3000的List做reverse,取樣30次。得到上面的結果。
我們的數據必須是正態分布,2組樣本的方差要一致。如果不一致,測試結果就無效,你必須找其它更厲害的工具(比如:R Tool)來分析。
測試方法
首先收集樣本
DataSet = eminstat:s(Name, Function, N)
: 運行Function
N
次,並收集每一次的運行時間做為DataSet。然后使用:
eninistat:x(ConfidenceLevel, DataSet1, [DataSet,...])
來分析樣本。
ConfidenceLevel
可以為[80.0,90.0,95.0,98.0,99.0,99.5]
一般使用95%就夠了。用於查表得到t-value。
結果分析
結果包括4個部分:
圖表(histogram)
對每一組樣本
要素(vitals)
孤立點分析(outlier analysis)
T-Test結果
圖表(Histogram)
x lists:reverse
+ tail_reverse
+--------------------------------------------------------------------------+
|xxxxxxx x ++++ +|
|xxxxx x ++++ |
|xxxx +++ |
|xxx +++ |
|xx +++ |
| x +++ |
| x + |
| x + |
| x + |
| x + |
| x + |
| x + |
| + |
| + |
| + |
||MA_| |
| |___MA____| |
+--------------------------------------------------------------------------+
把樣本的所有的數據都使用ASCII art的點到圖表上,可以看到數據總體分布。如果數據有重疊的,就使用*
來代替。
M
代表median中位數,
A
代表Average/Mean
平均數。
如果只顯示一個A
,就說明MA
重疊了。
|____…____|
代表假定為正態分布的數據集時表示1個標准偏差。越窄越穩定。
要素(Vitals)
Dataset: x N=30 CI=95.0000
Statistic Value [ Bias] (Bootstrapped LB‥UB)
Min: 8295.00
1st Qu. 8494.00
Median: 8700.00
3rd Qu. 9467.00
Max: 1.14400e+4
Average: 9088.53 [ -3.07012] ( 8849.00 ‥ 9447.13)
Std. Dev: 831.129 [ -28.0026] ( 608.188 ‥ 1129.95)
N
樣本的個數。
CI
上面說的信任等級[80.0,90.0,95.0,98.0,99.0,99.5]
Min
最小值
Max
最大值
Median
中位數
1st Qu
1/4的中位數位置(就是min與Median之前的中位數)
3st Qu
3/4的中位數位置(就是Median與Max之前的中位數)
Average
計算數據集的平均值以及標准偏差
為了比單點估計更精確,還提供了具有下限和上限的平均值和標准差的間隔。也就是說,eministat可以從這個特定樣本獲得的平均值是8849 ‥ 9447,而不是告訴平均值是9088作為一個點。
置信等級為95%並不意味着真實的總體平均值的可能性為95%,沒有任何程序可以做到這點,它代表:
如果你要重復這個實驗數千次,並且你會設置每個樣本的(不同的)置信區間,那么它有95%的概率是會包含真實的平均值。
系統使用偏差校正的加速引導方法(bias-corrected accelerated bootstrap method)來計算間隔的邊界。它還計算從引導程序到樣本參數的偏差。在上面的例子中,你會在Std中看到-28的偏差。這個偏差意味着一般引導程序(bootstrap)獲得比樣本估計值更小的標准偏差。
孤立點分析(Outliers)
Outliers: 0/1 = 1 (μ=9085.46, σ=803.126)
Outlier variance: 0.484430 (moderate)
emninstat
使用非常簡單計算孤立點的方法
IQR = Q3 - Q1
所以離中位數有1.5IQR的都會被認為是孤立點。
Lower/Upper
代表着較低的異常值低於平均值的個數/較高的異常值高於平均值的個數。
Outlier variance
孤立點的方差,代表着孤立點對最終結果的影響程度,等級為
unaffected‥slight‥moderate‥severe
如果結果為severe
,你就應該想辦法優化一下樣本,來避免這種影響。
影響孤立點的因素有很多:垃圾回收,CPU頻次變化,其它系統的后台任務。
T-Test
Difference at 95.0% confidence
1.85183e+4 ± 806.172
203.754% ± 8.87022%
(Student's t, pooled s = 1559.59)
上面代表着:
- 以上測試數據集符合正態分布,或近似於正態分布。
- 以上測試數據集有一樣的方差。
pooled :匯總(組合)方差,https://en.wikipedia.org/wiki/Pooled_variance。
1.85183e+4 ± 806.172
: 平均值(means) ± 間隔(interval)。
203.754% ± 8.87022%
203.754 = 1.85183e+4/9088.53(means) * 100
8.87022 = 806.172/9088.53(means) * 100
means是為第一個樣本的means。
所以上面的結果可以確切得出: lists:reverse
比尾遞歸
快得多,且穩定。
參考鏈接
http://www.statisticshowto.com/probability-and-statistics/t-test/
https://en.wikipedia.org/wiki/Pooled_variance
https://github.com/jlouis/eministat