動的配列の領域確保は事前に行うと速い

VBAの動的配列は領域の確保をRedimステートメントで上限値を指定する方法と、領域の拡張を行うRedim Preserveステートメントを指定する方法があります。

よく言われるのが、「領域の再確保(Redim Preserve)は遅いので、先に要素数を調べて領域の確保は最初に1回だけにしましょう」という話です。

これ自体はその通りで、Redim Preserveステートメントを行うと、拡張後の領域を探して元の領域から新しい領域にデータがコピーされて以降は新しい領域を利用する、といういくつもの段階があることから、時間が掛かる処理のため避けられるなら避けた方が処理速度の向上につながります。

では、Redimを事前にやっておくとどれぐらい速いのでしょうか? また、遅いと言われるRedim Preserveステートメントは、どれぐらい遅いのでしょうか?

Redimは常に高速

以下のコードは100万件のデータ領域を事前にRedimステートメントで確保します。

100万件の領域を確保したあとにループしていますが、処理時間は0.1秒前後です。

これぐらいであれば体感では全くわからず、高速です。

Redim Preserve自体はそんなに遅くない。ただし、データ型未指定だと遅い。

PCの性能によって結果は異なると思いますが、指定回数ループしながらRedim Preserveを行う以下のコードで、ループ回数を変更しながらどれぐらい処理速度に変化があるのかを計測してみました。

データ型指定をしない場合

以下は配列のデータ型を指定しない場合です。2行目の配列は暗黙のデータ型のVariantが指定されます。

結果は以下の通りになりました。

回数 秒数
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型を指定しています。上のコードとはその点のみが異なります。

データ型を指定した場合の結果は以下のようになりました。

回数 秒数
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で領域を拡張する場合は、配列のデータ型を適切に指定しておいた方がよい、ということになります。