動的配列の領域確保は事前に行うと速い
VBAの動的配列は領域の確保をRedimステートメントで上限値を指定する方法と、領域の拡張を行うRedim Preserveステートメントを指定する方法があります。
よく言われるのが、「領域の再確保(Redim Preserve)は遅いので、先に要素数を調べて領域の確保は最初に1回だけにしましょう」という話です。
これ自体はその通りで、Redim Preserveステートメントを行うと、拡張後の領域を探して元の領域から新しい領域にデータがコピーされて以降は新しい領域を利用する、といういくつもの段階があることから、時間が掛かる処理のため避けられるなら避けた方が処理速度の向上につながります。
では、Redimを事前にやっておくとどれぐらい速いのでしょうか? また、遅いと言われるRedim Preserveステートメントは、どれぐらい遅いのでしょうか?
Redimは常に高速
以下のコードは100万件のデータ領域を事前にRedimステートメントで確保します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Sub RedimTest() Dim ar() Dim iCount Dim i Dim a, b a = Timer iCount = 1000000 ReDim ar(iCount) For i = 0 To iCount ar(i) = "aaaaaaaaaaaaaaa" Next b = Timer Debug.Print b - a & "秒" End Sub |
100万件の領域を確保したあとにループしていますが、処理時間は0.1秒前後です。
これぐらいであれば体感では全くわからず、高速です。
Redim Preserve自体はそんなに遅くない。ただし、データ型未指定だと遅い。
PCの性能によって結果は異なると思いますが、指定回数ループしながらRedim Preserveを行う以下のコードで、ループ回数を変更しながらどれぐらい処理速度に変化があるのかを計測してみました。
データ型指定をしない場合
以下は配列のデータ型を指定しない場合です。2行目の配列は暗黙のデータ型のVariantが指定されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Sub RedimPreserveTest() Dim ar() Dim iCount Dim i Dim a, b a = Timer ReDim ar(0) iCount = 10000 For i = 0 To iCount ReDim Preserve ar(i) ar(i) = "aaaaaaaaaaaaaaa" Next b = Timer Debug.Print b - a & "秒" End Sub |
結果は以下の通りになりました。
回数 | 秒数 | 差 |
---|---|---|
10000 | 0.00293 | 0.0000000 |
20000 | 0.006104 | 0.0031738 |
30000 | 0.008057 | 0.0019531 |
40000 | 0.011963 | 0.0039062 |
50000 | 0.024048 | 0.0120850 |
60000 | 0.036987 | 0.0129395 |
70000 | 0.055054 | 0.0180664 |
80000 | 0.072021 | 0.0169678 |
90000 | 0.116943 | 0.0449219 |
100000 | 0.116943 | 0.0000000 |
200000 | 0.468018 | 0.3510742 |
300000 | 1.17395 | 0.7059324 |
400000 | 2.069946 | 0.8959960 |
500000 | 3.290039 | 1.2200930 |
600000 | 4.525024 | 1.2349850 |
700000 | 6.385986 | 1.8609620 |
800000 | 8.122925 | 1.7369390 |
900000 | 10.24707 | 2.1241450 |
1000000 | 12.69812 | 2.4510500 |
1100000 | 15.27002 | 2.5719000 |
1200000 | 18.21704 | 2.9470200 |
1300000 | 21.28796 | 3.0709200 |
1400000 | 24.75098 | 3.4630200 |
1500000 | 28.31104 | 3.5600600 |
1600000 | 32.23096 | 3.9199200 |
1700000 | 36.30798 | 4.0770200 |
1800000 | 40.74902 | 4.4410400 |
1900000 | 45.46997 | 4.7209500 |
2000000 | 50.19495 | 4.7249800 |
ずいぶん遅いですね。
このグラフの通り、10万件ぐらいまでは1秒未満程度ですが、そこを超えると処理秒数が上がっていきます。
100万件では20秒近くまで掛かっています。
データ型指定をする場合
以下は配列のデータ型としてString型を指定しています。上のコードとはその点のみが異なります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Sub RedimPreserveTest() Dim ar() As String Dim iCount Dim i Dim a, b a = Timer ReDim ar(0) iCount = 10000 For i = 0 To iCount ReDim Preserve ar(i) ar(i) = "aaaaaaaaaaaaaaa" Next b = Timer Debug.Print b - a & "秒" End Sub |
データ型を指定した場合の結果は以下のようになりました。
回数 | 秒数 | 差 |
---|---|---|
10000 | 0.001953 | 0.0000000 |
20000 | 0.004883 | 0.0029297 |
30000 | 0.006836 | 0.0019531 |
40000 | 0.009033 | 0.0021973 |
50000 | 0.010986 | 0.0019531 |
60000 | 0.013916 | 0.0029297 |
70000 | 0.015869 | 0.0019531 |
80000 | 0.018066 | 0.0021973 |
90000 | 0.020996 | 0.0029297 |
100000 | 0.022949 | 0.0019531 |
200000 | 0.083984 | 0.0610352 |
300000 | 0.158936 | 0.0749511 |
400000 | 0.271973 | 0.1130372 |
500000 | 0.405029 | 0.1330566 |
600000 | 0.641113 | 0.2360840 |
700000 | 0.8479 | 0.2067871 |
800000 | 1.049805 | 0.2019046 |
900000 | 1.268799 | 0.2189940 |
1000000 | 1.628174 | 0.3593750 |
1100000 | 1.930908 | 0.3027340 |
1200000 | 2.226074 | 0.2951660 |
1300000 | 2.64917 | 0.4230960 |
1400000 | 3.01709 | 0.3679200 |
1500000 | 3.516113 | 0.4990230 |
1600000 | 3.896973 | 0.3808600 |
1700000 | 4.306885 | 0.4099120 |
1800000 | 4.865967 | 0.5590820 |
1900000 | 5.471191 | 0.6052240 |
2000000 | 5.967041 | 0.4958500 |
データ型を指定するとかなり速くなっています。
Redim Preserveよりも代入時の暗黙の型変換の方が時間がかかる
上の結果をまとめると以下のようになります。
条件 | 秒数 |
---|---|
最初に要素数を指定する | 約0.1秒 |
データ型ありでのRedim Preserve | 約6秒 |
データ型なしでのRedim Preserve | 約50秒 |
やはり、処理の開始時に配列の要素数を指定して領域を確保するのは0.1秒と高速です。Redim Preserve は配列のデータ型を指定しておけば、そこそこの速さですが、指定しなかった場合はかなり遅くなります。
問題なのはRedim Preserveによる領域の再確保にかかる時間よりも、配列の各要素に代入する際の暗黙の型変換の方が時間が掛かっていることです。
結論として、事前に配列の要素数が分かるのであれば最初に領域を確保しておくのが一番高速で、そうではなくループ内でRedim Preserveで領域を拡張する場合は、配列のデータ型を適切に指定しておいた方がよい、ということになります。