リストHOME  リストOpen Source

第12回VBAプログラム豆知識

概要 ~ ちょっとした高速化 ~

 今回は、VBAプログラムの処理速度を改善する、ちょいテクについて説明しようと思います。皆さん、システムを利用していて、処理速度に対して、ストレスを感じたことはありませんか?
 私は、せっかちなので、こういう場面に遭遇すると、非常にイライラしてストレスを感じてしまいます。


 コンピュータ上で動作するシステムのパフォーマンス問題における、一つの原因は、メモリ不足、CPUの処理速度、ディスクのアクセス速度といった、物理的な環境によることが挙げられます。これについては、メモリの増設や、新型のパソコンに交換するといった方法で、概ね改善できるでしょう。


 しかし、原因が、開発者の設計したプログラム構造や実装が、よくない場合も可能性として、十分にありえるのです。今回は、プログラムの高速化について、説明しようと思うのですが、『高速なアルゴリズムを採用する』とか、そういった難しい話ではなく、ちょっとしたプログラムの記述の仕方で、改善でできる高速化について、焦点を絞ってお話ししてみたいと思います。


 それでは、例を使って、プログラムのパフォーマンス改善について、説明していきます。
以下がパフォーマンスを優先した場合に、良くない一つの例なのですが、皆さん、このプログラムのどこが悪いか分かりますでしょうか?プログラムは、引数に渡されたflgの値によって、ループの中で、処理Aと処理Bに、処理分岐しています。


 ちなみに、システム屋さんは、プログラムが処理される、プログラム文中の通り路(経路)のことをパスと呼びます。



'*** パフォーマンス低 ***
Public Sub LowPerform(ByVal flg As Boolean)
    Dim i As Long
    
    '1万回のループ
    For i = 1 To 10000
        If flg Then
            '*** flgがTrueのパス ***
        
            '* 処理A *
            Debug.Print "処理A"
        
        Else
            '*** flgがFalseのパス ***
            
            '* 処理B *
            Debug.Print "処理B"
            
        End If
    
    Next i

End Sub


 答えは、For文のループ内で、flgの値をチェックしている箇所です。プログラム中に、チェックの箇所が存在しているということは、チェック処理に少なからず時間が、かかっているということです。これって1万回のループなので、1万回のチェック分、処理に時間がかかってしまいます。


 ですので、処理速度を優先する場合は、以下のように実装してください。プログラムは変更されていますが、上記のプログラムと、全く同等の処理結果を、もたらすプログラムとなっています。



'*** パフォーマンス高 ***
Public Sub HighPerform(ByVal flg As Boolean)
    Dim i As Long
    
    If flg Then
        '*** flgがTrueのパス ***
        
        '1万回のループ
        For i = 1 To 10000
        
            '* 処理A *
        
        Next i
    
    Else
        '*** flgがFalseのパス ***
        
        '1万回のループ
        For i = 1 To 10000
            
            '* 処理B *
                        
        Next i
    
    End If
    
End Sub


 1万回の条件式の判定処理が、たったの1回に改善できました。このような場合、While文やFor文のループ回数が、増えれば増えるほど、パフォーマンスに、どんどん差がでてきます。『塵も積もれば…』というやつです。


 While文やFor文などの繰り返し回数の多いループの中では、if文の条件式判定処理を、できるだけ使用しないというのが、高速なプログラムを記述する一つのポイントです。


 但し、デメリットもあります。同じようなループのプログラム・コードが2箇所に記述され、非常にダラダラと冗長的なプログラムになってしまい、プログラムの可読性が下がってしまいます。プログラム自体が読みにくくなると、自身のコーディングミスにもつながり、開発効率も下がってしまいます。


 先の良くない例の方が、おそらく、プログラム自体は読みやすく、コーディング量も少なくて済むはずです。ですので、パフォーマンスを優先するべきか、プログラムの可読性やメンテナンス性を優先するべきかを、よく考えて実装することが大切です。


 似たような処理で、もう一つ例を使って説明してみたいと思います。以下の、LowPerform2関数の実装を見てください。これも1万回のループ処理なのですが、ループの最後(i=10000)の時だけ、少しことなる処理を行っています。



'*** パフォーマンス低 ***
Public Sub LowPerform2()
    Dim i As Long
    
    '1万回のループ
    For i = 1 To 10000
        
        '* 処理A *
        Debug.Print "処理A"
        
        'ループの最後だけ、少し異なる処理。
        If i = 10000 Then
            '*** i=10000のパス ***
            
            '* 処理B *
            Debug.Print "処理B"

        End If
    
    Next i

End Sub


 こういった場合は、以下のように、ループの回数を1回減らし、最後に処理Aと処理Bを記述します。ループを抜けた時には、i=10000になっていますので、上記と同等の処理になります。違うのは、ループの中で、If文によるチェック処理が、省略されているところです。If分による条件式の判定が10000回から0回になり、この分、処理が高速化されたことになります。



'*** パフォーマンス高 ***
Public Sub HighPerform2()
    Dim i As Long
    
    '9999回のループ
    For i = 1 To 9999
        
        '* 処理A *
        Debug.Print "処理A"
    
    Next i

    '*** i=10000のパス ***

    '* 処理A *
    Debug.Print "処理A"
            
    '* 処理B *
    Debug.Print "処理B"

End Sub


 次に、条件式判定を論理演算子で連結した場合の、お話しをしてみたいと思います。VBAでは、以下のようにA And Bや、C Or Dなどのように、複数の条件式判定を論理演算子で、連結すると、実は全て、条件式判定がなされてしまいます。ここに、無駄が発生してしまうのです。



    'VBAのIf分による条件式判定
    'A,B,C,Dは条件式(値を返却する関数も可)とします。
    If A And B Then
        Debug.Print "処理"
        
    End If

    If C Or D Then
        Debug.Print "処理"

    End If


 もう少し、具体的に説明すると、例えば、A And Bですが、条件式判定Aの結果がFalseだったとします。そうすると、Bの判定結果が、True/Falseのいずれになろうとも、If文内の処理が実行されることはありません。であれば、Bの条件式判定は、なされる必要がないのですが、VBAの仕様上、AもBも、条件式判定されてしまいます。また、論理演算子の種類は異なりますが、C Or Dの場合も、同様のことが言えます。

※ちなみに、C言語は、一つでも、条件式判定が不一致となった時点で、次の処理に移行します。


 ですので、上記のIf文をパフォーマンスを優先して書き直すと、以下のようになります。こうすれば、処理が実行されない条件式が存在することになり、少なからず、パフォーマンスが向上します。



    'A And Bは、以下のように記述します。
    'Bが判定されずに済むケースが発生します。
    If A Then
        If B Then
            Debug.Print "処理"
        
        End If

    End If


    'C Or Dは、以下のように記述します。
    'Dが判定されずに済むケースが発生します。
    If C Then
        Debug.Print "処理"

    ElseIf D Then
        Debug.Print "処理"

    End If
	


 VBAでは、If文一つに、複数の条件式判定を記述しないほうが、総合的にパフォーマンスが向上します。


 このことをもう少し、具体的な例を使って説明したいと思います。以下が、And演算の場合です。処理内容は、100回のループで、iが60~90の場合に、処理を実行するサンプルです。条件式Aを i > 60、条件式Bを i < 90 として、説明を続けます。


'条件式A:i > 60
'条件式B:i < 90

'*** パフォーマンス低 ***
Public Sub LowPerform_And()
    Dim i As Long
    
    '100回のループ
    For i = 1 To 100
        'どちらの条件式も100回判定されます。
        If i > 60 And i < 90 Then
            Debug.Print "処理"
        
        End If
    
    Next i

End Sub

'*** パフォーマンス高 ***
Public Sub HighPerform_And()
    Dim i As Long
    
    '100回のループ
    For i = 1 To 100
        '100回判定されます。
        If i > 60 Then

            'iが60より大きい時、40回判定されます。
            If i < 90 Then
                Debug.Print "処理"
        
            End If
            
        End If
    
    Next i

End Sub


 LowPerform_And関数の場合、条件式A、B、いずれも100回判定されてしまいます。HighPerform_And関数のように処理を書き換えた場合、条件式Aは100回、条件式Bは40回判定されるようになり、条件式Bの判定が60回分、処理時間が改善されたことになります。

And演算のパファーマンス改善


 LowPerform_And関数とHighPerform_And関数は、まったく同等の処理結果をもたらす関数ですが、プログラムの書き方、一つで処理速度に差が出てきます。


 今度は、Or演算について、例を使って、考えて見ましょう。以下の、処理内容は、100回のループで、iが60より小さい時、もしくは、90より大きい時に、処理が実行されるサンプルです。条件式Aを i < 60、条件式Bを i > 90 として、説明を続けます。


'条件式A:i < 60
'条件式B:i > 90

'*** パフォーマンス低 ***
Public Sub LowPerform_Or()
    Dim i As Long

    '100回のループ
    For i = 1 To 100
        'どちらの条件式も100回判定されます。
        If i < 60 Or i > 90 Then
            Debug.Print "処理"
        
        End If
    
    Next i

End Sub

'*** パフォーマンス高 ***
Public Sub SpeedHigh_Or()
    Dim i As Long

    '100回のループ
    For i = 1 To 100
        '100回判定されます。
        If i < 60 Then
            Debug.Print "処理"
        
        'iが60以上の時、41回判定されます。
        ElseIf i > 90 Then
            Debug.Print "処理"
                
        End If
    
    Next i

End Sub

 LowPerform_Or関数の場合、条件式A、B、いずれも100回判定されてしまいます。HighPerform_Or関数のように処理を書き換えた場合、条件式Aは100回、条件式Bは41回判定されるようになり、条件式Bの判定が59回分、処理時間が改善されたことになります。


 Or演算についても、And演算と同様に、プログラムの書き方、一つで処理速度に差が出てきます。

Or演算のパファーマンス改善


 今回のお話しは、ここまでにしたいと思います。何気に、記述しているプログラムも、ちょこっと変更するだけで、プログラムの処理速度に差がでるものなのです。これが、大容量のデータを扱うループの中だったりすると、とんでもない差が出てくるわけです。そうなんです、『ちょっと』が、『ごっつ』なんです。



第11回  <  >  第13回



所感

 今回は、プログラムの書き方、一つで、処理速度に差がでるというお話しをしましたが、メリットだけでは、なく冗長的なプログラムになって、解読しにくいプログラムになるというデメリットも説明しました。


 処理速度が求められている場面なのかどうかを、良く考えて、どのような実装にするかを検討して頂けたらと思います。分かりやすいプログラムを書くというのも非常に重要です。


 以前にもお話ししましたが、一番大切なのは、正しくプログラムが動作することです。


連絡先

 ご意見・ご要望等ありましたら、画面最下部のメールアドレスまでご連絡ください。




エクセルバックアップ・ページのフッター
管理者のメールアドレス