性能テストの結果にダメ出しをしてください
今回は少し専門的な内容です。
あるシステムの改修時に行った性能テストの結果から、劣化は起きていないことを証明したいと思います。
部下の提出した結果で判断できますか?
「性能」とは、対象となるシステムが「処理結果を返す能力」を指します。
例えば、バーゲン時期になると人気ECサイトのアクセスが集中して画面が次に進まない、なんてことがあります。
これは能力が3なのに、処理の要請が10来ているような状況を意味しています。「処理結果を返す能力」が追いついておらず、処理待ちの渋滞が発生しているわけです。
性能テストはこうした事態を防ぐため、①「あらかじめ定義した能力」を②「実際に発揮できるか」を確認するテストです。
ちなみに性能テストで見るべきポイントはスループットとレスポンスです。渋滞の例で言えば、スループットとは車が何台通過したか、レスポンスとは車は何秒で通過したかを意味しています。
さて、今回はレスポンスに関する性能テストに焦点をあてます。 あるシステムは、ユーザーが確認ボタンを押してから1秒以内に裏側で処理を実行し、完了画面を提示しなければならない決まりがあります。1秒を超えると処理が途中終了せざるを得ず、ユーザーの満足度は下がると言われています。
その裏側での処理を一部改修したので、性能が劣化していないかテストで確認しなければなりません。 部下が提出した結果は以下の通りです。
======================
改修前後で200回ずつ処理を行い、その時間を計測したところ、大きな変化は無いことを確認できた。
1秒掛かった処理も1件も無い。よって、性能は劣化していないと考える。
改修前:0.232073秒
改修後:0.244364秒
(どちらも200回の平均値である)
======================
この判断は合っているでしょうか?間違っているでしょうか?ダメ出しをして下さい。
ヒストグラム&累積相対度数&箱ひげ図で可視化する
私なら2点のダメ出しをすると思います。
1点目はこの場合に平均を使ってはいけない。2点目は課題設定が間違っている。
特に2点目は本体サイトで課題解決家でもある柏木さんとのインタビューにあった通り、課題設定が曖昧な分析ほど危険なものはありません。
今回のテストで見るべき点は、①改修を加えても性能は劣化していないと言えるのか、②劣化していたとして制約に影響が出るほどか、この2点です。
したがって改修前後の平均値で見て0.012秒でも違いがあれば、それは劣化です。そして、この数字を正とするなら1秒には遠いので影響は無いと判断できます。
書きぶりの話かもしれませんが、ここを整理しておかないとミスリードに繋がりかねません。
問題は、これが偶然かどうか判断できないことです。
とくに平均はデータに偏りがあった場合に引っ張られるという性質を持ちます。200回のテストの中で、1秒かかった処理は無いとはいえ、0.1秒台と0.7秒台が混在している可能性もあるのです。
そうなると、あと0.3秒で上限に引っかかるので、影響が無いとは言い切れません。
したがって、まずは平均で全体を丸めた値を見るのではなく、ヒストグラムで全体の傾向を見るべきです。実際にやってみます。以降はRでの操作を想定しています。
======================
d <- read.csv("./data.csv",header=TRUE)
// 基本統計量で区間数の上限と下限を確認
summary(d);
second
Min. :0.1467
1st Qu.:0.2374
Median:0.2467
Mean :0.2382
3rd Qu.:0.2542
Max. :0.4192
// ヒストグラムの作成(範囲は0.1〜0.45、少し多いですが14区間で作成します)
hist(d.old$second,breaks=seq(1000,4500,500),main="green=new,red=old", col="#99343540",ylim=c(0,120))
hist(d.new$second,breaks=seq(1000,4500,500),main="new second", col="#53995240", add=T)
# add=Tと書くことでグラフに上書きされる。
# 色指定の末尾に00〜99の数字を追記すれば透明度を選択できる。
======================
改修前は0.225秒〜0.25秒に集中していますが、改修後は0.25〜0.275秒に集中していることがわかりました。
処理におよそ0.225秒~0.275秒かかるにしろ、改修前後で少しだけ傾向と変わっています。これが改修の影響かもしれません。
もう1つ、改修後はデータの範囲が広く(0.40秒~0.425秒まである)、これが平均値を押し上げる要因になっていることがわかります。平均値とヒストグラムを見比べてイメージが合致し無い要因でもあります。
この結果を累積相対度数で表してみましょう。
======================
// 累積相対度数
Fn.old <- ecdf(d[d$type=="old",]$second)
Fn.new <- ecdf(d[d$type=="new",]$second)
plot(Fn.old,col="#99343575", main="green=new, red=old", xlim=c(0.1,0.45))
plot(Fn.new,col="#53995275", add=T)
======================
線がほとんど重なっていませんね。
この結果からするに、改修前後で少なからず性能「劣化」はあったと見るべきなのでしょう。まぁ、何かしらの処理を追加しているので当然と言えば当然ですね。
最後に箱ひげ図を使って可視化してみましょう。
boxplot(d$second~d$type);
改修前後で見比べると、改修後のほうが四分位範囲も広く、中央値も少しだけ高いことがわかります。
気になるのは最小値以下の外れ値が改修前後ともに一定値あることです。累積相対度数で見ると全体の約20%程度あることがわかります。
1つ考えられるのはブラウザのキャッシュか何かで高速化が計られているということ。そうするとテスト環境は正しかったのか?という疑問を抱きます。
標準誤差で「偶然」の可能性を考慮する
母集団の母平均がµとして、テストを繰り返すことで標本平均を算出しました。回数が多ければ多いほどµに近付けると考えれば、200回の結果は偶然差異が出てしまった、という考え方もあるでしょう。
つまり200回の結果で偶然差が出てしまったけれど、1000回まで実施するとその差が埋まるのでは無いか?という可能性です。
これは標準誤差を求めれば簡単です。
標準誤差は母平均µの区間推定量を表します。つまり、あるデータ量から母平均µを表したいときに、標準偏差を用いれば「だいたい(95%の確率で)、ここからここぐらいの間かな?」と言えるのです。計算式で言えば以下のとおりです。
改修前後それぞれの標準誤差を算出してみました。
======================
//前処理を行う
require(plyr)
d.summary <- ddply(d, "type", summarize,
d.mean = mean(second),
d.sd = sd(second),
low = d.mean - 2 * d.sd/sqrt(NROW(second)),
up = d.mean + 2 * d.sd/sqrt(NROW(second)))
d.summary
===========================================
type d.mean d.sd low up
1 new 0.244364 0.03699586 0.239132 0.249596
2 old 0.232073 0.03210975 0.227532 0.236614
===========================================
// 標本平均±標準誤差をggplot2で表示
require(ggplot2)
ggplot(d.summary, aes(x=d.mean, y=type)) + geom_point() + geom_errorbarh(aes(xmin=low,xmax=up), height = .2)
======================
重なり合う部分が無いので、改修前後で性能劣化は起きていると言えそうです。
ただし、制約となる1秒にまで届いていないので、結果としては問題無さそうです。もっとも95%範囲なので、何かしらの異常値で100回に1回は発生する可能性を秘めています。
この標準誤差は標本の数が大きければ大きいほど小さくなります。逆に、標本の数が少なければ大きくなります。数が多い=標本平均として母集団を反映している、とお伝えすれば理解しやすいでしょうか。
ところで、性能テストって何回やればいいんですか?という疑問がここでわいてくると思います。
必要な標本数を算出する計算式は「(1.96/目標誤差)^2 *P*(1 - P)」という式があるのですが、話が逸れてしまうので今度にします。
t検定で「偶然」の可能性を考慮する
ほぼ結論が出ていますが、最後に仮設検定を行います。仮設検定とはデータから立てた仮設を証明する方法で、推計統計学の醍醐味の1つです。
例えば今回の例で言うと「このデータには差がある」と言うためには、まず「データには差は無く、今回は偶然生じた」という仮設を立てます。そして仮説が「差が偶然生じたという確率(p値)は低い」という論理で棄却することで、差があることを証明します。二重否定と言います。
H1(帰無仮説):データには差は無く、今回は偶然生じた
H0(対立仮説):データには差がある
ややこしいですね。素直に好きと言えばいいのに、好きじゃないとは言えないかもしれないと表現するのです。西野カナの歌詞か!と突っ込みたくなります。
「無いこと(帰無仮説)の証明はできない」ので、「差が無いとは言えない」ことを立証するわけです。ちなみに、それでも「差がある」と明確に言えるわけではありません。言えないだけです。思わせぶりな態度で男を弄ぶ小悪魔、決して好きとは言わない、それが仮説検定。
では今回は2群の差を比較するためにt検定を用います。
ちなみに、t検定の中でも分散が等しいか?(等分散か?)で検定手法が変わるので、事前に等分散の検定を行います。
この検定の帰無仮説は「対象の2群の分散に差はないこと」なのでp<0.05なら帰無仮説は棄却されて対象の2群は不等分散、p>=0.05なら等分散と言えます。
var.test(second ~ type,data=d) =========================================== F test to compare two variances data: second by type F = 1.3275, num df = 199, denom df = 199, p-value = 0.04633 alternative hypothesis: true ratio of variances is not equal to 1 95 percent confidence interval: 1.004629 1.754118 sample estimates: ratio of variances 1.327493 ===========================================
微妙な結果になりました。1%有意水準なら棄却できず、5%有意水準なら棄却できます。(ギリギリですが)
ちなみに学術論争は他に譲るとして、そもそも帰無仮説が棄却されなくても帰無仮説が証明されたとは言えないので(等分散が保証されているわけではないので)、気にせず等分散を前提としないウェルチのt検定を実施するという考え方もあるようです。
http://oku.edu.mie-u.ac.jp/~okumura/blog/node/2262 http://aoki2.si.gunma-u.ac.jp/lecture/BF/index.htmlそこで、そのまま等分散を仮定しないt検定を行います。
t.test(second ~ type,data=d, var.equal=FALSE) =========================================== Welch Two Sample t-test data: second by type t = 3.5483, df = 390.27, p-value = 0.0004349 alternative hypothesis: true difference in means is not equal to 0 95 percent confidence interval: 0.005480749 0.019101251 sample estimates: mean in group new mean in group old 0.244364 0.232073 ===========================================
データが同じという帰無仮説が棄却されたので、差が無いとは言えないことが検定でもわかりました。
まとめ
今回の結果から、次のことが言えます。
======================
①改修前後で200回ずつ処理を行い、処理時間の差が無いとは言えないことが解った。箱ひげ図や累積相対度数図から考えても、そう見て良いだろう。
②ただしヒストグラムや箱ひげ図から見て、処理に0.5秒かかっておらず、閾値に達しているとは言えない。
③とはいえ、箱ひげ図の結果からして都度フレッシュなテスト環境で性能テストが実施されたとは言い難い。どのようなテスト実施環境で実行されたのか確認するべきである。
======================
テストだって、ちゃんとやれば色々解るんです!