#37 | If〜ElseステートメントとIIf関数の比較 | |||||||||||||||||||||||||||
VBAで条件分岐を行うには、If〜ThenステートメントやIf〜Elseステートメントを使います。一方、IIf関数は、条件によって異なる単一の値を返すような場合にIf〜Elseステートメントの代用として使える関数です。If〜Elseステートメントのように各条件ごとに複数行の命令を記述することはできませんが、コードをシンプルに記述できる便利な関数です。条件による代入値の切り替えを1行で表現できるとともに、そのような1つの変数への代入操作のコードであることが分かりやすくなるというメリットがあります。 RetValue = IIf(Expr, TruePart, FalsePart) の2番目と3番目に書かれた処理が常に両方実行されるためです。これらが単なる値であれば大きな問題はないはずですが、組み込み関数やFunctionプロシージャを呼び出しているような場合には、常にそれらが実行されることになってしまいます。特にFunctionプロシージャでテーブル操作などの長い処理、時間のかかる処理を行って1つの返り値を求めているような場合には、その両方が実行されることはパフォーマンスに大きな影響を与えることも考えられます。 そこでここでは、If〜ElseステートメントとIIf関数の実行時間を実際に比較してみるとともに、IIf関数の内部動作、つまり2つの引数が両方実行されるということを検証してみたいと思います。 それではまず手始めに、非常に簡単なループ処理によって2つの処理時間を比べてみたいと思います。次のテスト用コードでは、ループ変数ilngLoopを使って9999999回の繰り返し処理を行っています。そして、ilngLoopの値によって、lngRetという変数に異なる値を代入しています。 Sub iftest_1() 'IfとIIfの簡単な処理の比較(単一の比較演算) Dim ilngLoop As Long Dim lngRet As Long ts_Watch "処理開始", True For ilngLoop = 1 To 9999999 If ilngLoop > 100000 Then lngRet = 1 Else lngRet = 2 End If Next ilngLoop ts_Watch "If〜Elseステートメント" For ilngLoop = 1 To 9999999 lngRet = IIf(ilngLoop > 100000, 1, 2) Next ilngLoop ts_Watch "IIf関数" End Sub その結果は次のようなものになりました。 ご覧のように、基本的にはまったく同じ"条件による代入値の切り替え"を行っているだけにも関わらず、2倍以上の開きが出ています。このことから、IIf関数内でFunctionプロシージャを呼び出すまでもなく、単なる固定値の代入であってIIf関数は時間がかかるということが分かります。 さて、続いて、同様のループ処理ですが、今度は条件式を2つにしてみます。IIf関数の場合は引数の中にさらにIIf関数を含めるという入れ子状になります。 そのテスト用コードおよび実行結果は以下のとおりです。 Sub iftest_2() 'IfとIIfの簡単な処理の比較(2つの比較演算) Dim ilngLoop As Long Dim lngRet As Long ts_Watch "処理開始", True For ilngLoop = 1 To 9999999 If ilngLoop > 100000 And ilngLoop < 500000 Then lngRet = 1 Else lngRet = 2 End If Next ilngLoop ts_Watch "If〜Elseステートメント" For ilngLoop = 1 To 9999999 lngRet = IIf(ilngLoop > 100000, IIf(ilngLoop < 500000, 1, 2), 2) Next ilngLoop ts_Watch "IIf関数" End Sub IIf関数の方が遅いのは当然のこととして、ここで着目すべきは、最初のテスト結果との違いです。 If〜Elseステートメントの方は17%の増加に過ぎませんが、IIf関数は約100%の増加、つまり2倍にまで処理時間が増大しています。つまり、"IIf関数を入れ子にすると倍々で処理時間が増える"と推測することができます。これが"IIf関数は多用すべきでない"という根拠といえそうです。.....もっとも、入れ子のIIf関数はプログラムの視認性も悪くなるのですが さてここで、IIf関数の内部メカニズムについて検証を行っておきましょう。 次のようなコードを実行してみます(実際に実行するのはiftest_3)。ここではIIf関数の2番目と3番目の引数で、引数をイミディエイトウィンドウに出力するだけのFunctionプロシージャ「dummy」を呼び出しています。 Sub iftest_3() 'IIf関数の動作の仕組みの検証 Dim a a = 1 If a = 1 Then dummy "Ifその1" Else dummy "Ifその2" End If a = IIf(a = 1, dummy("IIfその1"), dummy("IIfその2")) End Sub Function dummy(strText As String) Debug.Print strText End Function 実行後のイミディエイトウィンドウは下図のとおりです。 当然のことながら、変数「a」が"1"のときは、Elseステートメント内のコードは実行されません。しかしIIf関数はそうはなりません。「a = 1」という条件式の真偽に関わらず2つの式両方が実行されているのは明らかです。 このことからも、パフォーマンス的な理由だけでなく、組み込み関数やプロシージャの"予期せぬ"実行による問題を防ぐためには、IIf関数内での関数やプロシージャ呼び出しには十分注意する必要があるといえるでしょう。 それでは、実際にIIf関数内から組み込み関数を呼び出した場合の処理時間を測定してみることにしましょう。 Sub iftest_4() '関数を呼び出す場合のIfとIIfの比較 Dim ilngLoop As Long Dim dblRet As Double ts_Watch "処理開始", True For ilngLoop = 1 To 9999999 If ilngLoop > 100000 Then dblRet = 1 / Sin(ilngLoop) Else dblRet = 1 / Cos(ilngLoop) End If Next ilngLoop ts_Watch "If〜Elseステートメント" For ilngLoop = 1 To 9999999 dblRet = IIf(ilngLoop > 100000, 1 / Sin(ilngLoop), 1 / Cos(ilngLoop)) Next ilngLoop ts_Watch "IIf関数" End Sub ここでは、特に深い意味はありませんが、小数を扱い複雑な処理を行っていそうなSin関数とCos関数を呼び出しています。これまでの結果から、If〜Elseステートメントの方は、Sin関数・Cos関数のいずれか1つだけが呼び出され、IIf関数の方は常に両方が呼び出されることになりますので、後者の方は少なくても2倍の処理時間がかかることが予想されます。 そしてその結果は次のようなものとなりました。 ご覧のように、単に値を代入するのではなく、関数を実行していることから、どちらも最初のテストに対して実行時間が2倍以上となっています。IIf関数が遅いのはここでも言うまでもありませんが、その分、両者の時間差も大きくなることが分かります。したがって、ただでさえ時間のかかる関数やプロシージャを、数学的な処理などにおいてループで何度も呼び出す場合にはコードの見た目よりパフォーマンス重視でIf〜Elseステートメントを使った方がよいといえます。 とはいえ、違う見方をすれば、プロシージャの中でたった1回だけ分岐処理を行う程度のものであれば、1回当たりについては次表程度の違いしかないので、コードの見易さを重視してIIf関数を使うようにしても何ら影響はないといえるでしょう。 実行結果1回当たりの換算表 |
||||||||||||||||||||||||||||
|
Copyright © T'sWare All rights reserved |