午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

条件分岐最適化、処理時間の計測

 
条件分岐最適化
www.initialt.org/takehiro-switch-case.PDF
 
この記事を読んでエクセルVBAでも試してみた
xが0、1、2、3のどれかのとき
3のとき
2のとき
0か1のとき
で条件分岐させるときに

        If x = 3 Then
 
        ElseIf x = 2 Then
 
        Else
 
        End If
↑これが早いのか

↓今までどおり(昔ながら)のこれが早いのか
        If x < 2 Then
 
        ElseIf x = 2 Then
 
        Else
 
        End If

いまどきのCPUなら上のほうが早いっていう結果になっているけど
エクセルVBAでCPUがPhenomⅡ X3の環境だと下のほうが早かった
原因はエクセルVBAPhenomⅡか僕の書き方なのか、どれなんだろう
 
 
時間計測に使った関数は
GetTickCount
QueryPerformanceCounter
QueryPerformanceFrequency
Queryが付く関数はGetTickCount関数より正確らしい?
けど調べてもよくわからなかった
 
計算途中でCPUの周波数が変化しないように電源の管理で固定するように設定して

f:id:gogowaten:20191018131251p:plain

最初は最低クロックの0.8GHzに固定して計測
電源の管理画面は
コントロールパネル→システムとセキュリティ→電源オプション→プラン設定の変更→詳細な電源設定の変更→プロセッサの電源管理
 
百万回判定するのにかかった時間
イメージ 2
GetTickCount関数で取得した値はミリ秒単位らしい
単位はよくわからないけどIfでの条件分岐は昔ながらの方が早いのがわかる
何回か試してもだいたい同じ結果になる
 
同じく百万回を
イメージ 4
QueryPerformanceCounterとQueryPerformanceFrequencyを使って計測
開始と終了時間をQPCountで取得して
(終了 - 開始)の値をQPFrequencyで割った数値
単位は更に良くわからないけど昔ながらの方が早い
 
 
Select Caseでも測ってみた
イメージ 7
Select Caseでも昔ながらの方が早いけど
一番早いのはIfの昔ながら
CPUクロックを最大値に固定

f:id:gogowaten:20191018131320p:plain

3.0GHzに固定して計測
 
計測した結果
イメージ 6
なんだこれE-02とか付いている、桁があふれた?
それでもIfの昔ながらが一番早そう
 
さっき(午後9時47分)追記ここから
乱数取得の方法が間違っていたので修正して計測しなおした
3.0GHz固定でIFで百万回にかかった時間
78なら0.078秒

f:id:gogowaten:20191018131332p:plain

結果は変わらず今までどおり(昔ながら)の方が早い
それにしても乱数っていってもきれいに4等分になるんだなあ
Int(Rnd * 4)ってあっているのかな?
 
.NET Visual Basicでも試してみた
条件分岐最適化、処理時間の計測 Visual Basic編 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
 
追記ここまで
 
普段の設定
イメージ 5
いつもは5%から100%で使っている
これで計測しても3.0GHzに固定した時と変わらない数値が出る
参照したところ
3.5 1/1000秒単位の時間を正確に取得- Excel VBA ゲーム Tips
 
excel vba 関数の引数と戻り値を配列にする - yoshiya_naの日記
 
単精度小数点型の乱数を返す(Rnd関数):Excel VBA|即効テクニック|Excel VBAを学ぶならmoug
 
エクセルVBAマクロ - 便利な関数 - 乱数の生成
 
 

標準モジュールの一番先頭に書く呪文

f:id:gogowaten:20191018131346p:plain

 

Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function QueryPerformanceCounter Lib "kernel32" _
    (lpPerformanceCount As Currency) As Boolean
Declare Function QueryPerformanceFrequency Lib "kernel32" _
    (opFrequency As Currency) As Boolean
 
0から3までのランダムな整数の配列作成用

f:id:gogowaten:20191018131357p:plain

Function myRnd(ValAry() As Long) As Long()
'0から3までのランダムな整数の配列作成
    Dim i As Long
    Randomize
    'Const Cnt = 10000
    Dim Cnt As Long
    Cnt = UBound(ValAry)
    For i = 0 To Cnt
         ValAry(i) = CStr(CInt(Rnd * 3))
         ValAry(i) = Int(Rnd * 4)
    Next
    myRnd = ValAry()
    
End Function
GetTickCount関数でIf条件分岐百万回の計測

f:id:gogowaten:20191018131407p:plain

 
Sub testNewVsOld()
    Dim myAry() As Long
    Const Cnt As Long = 1000000 '計算回数指定
    ReDim myAry(Cnt)
    myAry = myRnd(myAry)
    
    Dim itiZero As Long, ni As Long, san As Long
    Dim myStart As Long, myTimeNew As Long, myTimeOld As Long
    
     'いまどきの条件分岐
    myStart = GetTickCount
    For i = 0 To Cnt
        If myAry(i) = 3 Then
            san = san + 1
        ElseIf myAry(i) = 2 Then
            ni = ni + 1
        Else
            itiZero = itiZero + 1
        End If
    Next
    myTimeNew = GetTickCount - myStart
    itiZero = 0: ni = 0: san = 0

     '今までどおりの条件分岐
    myStart = GetTickCount
    For i = 0 To Cnt
        If myAry(i) < 2 Then
            itiZero = itiZero + 1
        ElseIf myAry(i) = 2 Then
            ni = ni + 1
        Else
            san = san + 1
        End If
    Next
    myTimeOld = GetTickCount - myStart

    MsgBox myTimeNew & "(今どきのIF)" & vbNewLine _
            & myTimeOld & "(昔ながらのIF)" & vbNewLine & vbNewLine & _
            itiZero & "(1or0の個数)" & vbNewLine & _
            ni & "(2の個数)" & vbNewLine & _
            san & "(3の個数)"
   '結果は今までどおりの古いやり方(昔ながら)のほうが1割くらい早かった
   '78、63
   'CPUはAMD Phenom X3 720 @3.0GHz
   
End Sub
 
 
 
 
QueryPerformanceCounterを使って計測

f:id:gogowaten:20191018131420p:plain

 
Sub testQPC()
'QueryPerformanceCounterを使って計測
    Dim myAry() As Long
    Const Cnt As Long = 1000000
    ReDim myAry(Cnt)
    myAry = myRnd(myAry)
    
    Dim itiZero As Long, ni As Long, san As Long
    Dim myStart As Currency, myEnd As Currency, Freq As Currency
    Dim myTimeNew, myTimeOld
    
    'いまどきの条件分岐
    Call QueryPerformanceFrequency(Freq)
    Call QueryPerformanceCounter(myStart)
    For i = 0 To Cnt
        If myAry(i) = 3 Then
            san = san + 1
        ElseIf myAry(i) = 2 Then
            ni = ni + 1
        Else
            itiZero = itiZero + 1
        End If
    Next
    Call QueryPerformanceCounter(myEnd)
    myTimeNew = (myEnd - myStart) / Freq
    
    '今までどおりの条件分岐
    Call QueryPerformanceFrequency(Freq)
    Call QueryPerformanceCounter(myStart)
    For i = 0 To Cnt
        If myAry(i) < 2 Then
            itiZero = itiZero + 1
        ElseIf myAry(i) = 2 Then
            ni = ni + 1
        Else
            san = san + 1
        End If
    Next
    Call QueryPerformanceCounter(myEnd)
    myTimeOld = (myEnd - myStart) / Freq
    
    '結果表示
    MsgBox myTimeNew & "(今どき)" & vbNewLine _
            & myTimeOld & "(昔ながら)"
'こっちでも今までどおりのほうが早い
'CPUはAMD Phenom X3 720 @3.0GHz

End Sub
 
 
 
IfとSelect CaseそれぞれをQueryPerformanceFrequencyと
QueryPerformanceCounterを使って計測

f:id:gogowaten:20191018131432p:plain

 

Sub testQPCSelectCase()
'QueryPerformanceCounterを使って計測
    Dim myAry() As Long
    Const Cnt As Long = 1000000
    ReDim myAry(Cnt)
    myAry = myRnd(myAry)
    
    Dim itiZero As Long, ni As Long, san As Long
    Dim myStart As Currency, myEnd As Currency, Freq As Currency
    Dim myTimeNew, myTimeOld, mySelectNew, mySelectOld
    
    'いまどきの条件分岐
    Call QueryPerformanceFrequency(Freq)
    Call QueryPerformanceCounter(myStart)
    For i = 0 To Cnt
        If myAry(i) = 3 Then
            san = san + 1
        ElseIf myAry(i) = 2 Then
            ni = ni + 1
        Else
            itiZero = itiZero + 1
        End If
    Next
    Call QueryPerformanceCounter(myEnd)
    myTimeNew = (myEnd - myStart) / Freq
    
    '今までどおりの条件分岐
    Call QueryPerformanceFrequency(Freq)
    Call QueryPerformanceCounter(myStart)
    For i = 0 To Cnt
        If myAry(i) < 2 Then
            itiZero = itiZero + 1
        ElseIf myAry(i) = 2 Then
            ni = ni + 1
        Else
            san = san + 1
        End If
    Next
    Call QueryPerformanceCounter(myEnd)
    myTimeOld = (myEnd - myStart) / Freq
    
    
    'Select Case いまどき
    Call QueryPerformanceFrequency(Freq)
    Call QueryPerformanceCounter(myStart)
    For i = 0 To Cnt
    Select Case True
        Case myAry(i) = 3
            san = san + 1
        Case myAry(i) = 2
            ni = ni + 1
        Case Else
            itiZero = itiZero + 1
    End Select
    Next
    Call QueryPerformanceCounter(myEnd)
    mySelectNew = (myEnd - myStart) / Freq
    
    'Select Case 今までどおり
    Call QueryPerformanceFrequency(Freq)
    Call QueryPerformanceCounter(myStart)
    For i = 0 To Cnt
    Select Case True
        Case myAry(i) < 2
            itiZero = itiZero + 1
        Case myAry(i) = 2
            ni = ni + 1
        Case Else
            san = san + 1
    End Select
    Next
    Call QueryPerformanceCounter(myEnd)
    mySelectOld = (myEnd - myStart) / Freq
    
    
    '結果表示
    MsgBox myTimeNew & "(今どき)" & vbNewLine _
            & myTimeOld & "(昔ながら)" & vbNewLine _
            & mySelectNew & "(今どきSelectCase)" & vbNewLine _
            & mySelectOld & "(昔ながらSelectCase)"
'こっちでも今までどおりのほうが早い
'CPUはAMD Phenom X3 720 @3.0GHz

End Sub
 
ダウンロード:いまどきのCPUのための条件分岐最適化テスト.xlsm