パソコン通信時代に書いた、この「VBA高速化テクニック」の中で、間違って伝わっているな~と感じていたのは、冒頭の「画面を止める」と、これ「配列を使う」です。90年代のパソコンはスペックが低かったです。CPUは遅いし、メモリも少なく低速、Windowsの画面描画機能も遅かったし、そもそもExcelもそれほど速くはなかったです。そんな中で、大量のセルにアクセスするのなら、一度配列に入れて配列を操作する方が速いよって話でした。でも、何となく「配列に入れると速い」ということだけが一人歩きして、とにかく何でも「配列は速い!配列は速い!配列!配列ぅ!」って言われてます。

ちょっとやってみましょうか。まずは単純に、データを探す操作から。A列に200,000個の名前が入っています。この中から"田中"を探して、該当するB列の数値を取得します。ちなみに"田中"は一番下のセルA200000にあります。
Sub Test1()
Dim i As Long, A As Long
For i = 1 To 200000
If Cells(i, 1) = "田中" Then A = Cells(i, 2)
Next i
End Sub
Sub Test2()
Dim i As Long, A As Long, B As Variant
B = Range("A1:B200000")
For i = 1 To 200000
If B(i, 1) = "田中" Then A = B(i, 2)
Next i
End Sub
でも、ただ探すだけならFindステートメントでも可能です。ついでに、Findステートメントの速度も計測してみましょう。
Sub Test3()
Dim i As Long, A As Long, FC As Range
Set FC = Range("A1:A200000").Find(What:="田中")
A = FC.Offset(0, 1)
End Sub
| 回 | 力わざ | 配列 | Find | % |
|---|---|---|---|---|
| 1 | 0.734 | 0.078 | 0.125 | 160.3% |
| 2 | 0.766 | 0.078 | 0.141 | 180.8% |
| 3 | 0.656 | 0.078 | 0.140 | 179.5% |
| 4 | 0.734 | 0.094 | 0.141 | 150.0% |
| 5 | 0.750 | 0.094 | 0.140 | 148.9% |
| 6 | 0.718 | 0.078 | 0.140 | 179.5% |
| 7 | 0.704 | 0.094 | 0.156 | 166.0% |
| 8 | 0.750 | 0.078 | 0.141 | 180.8% |
| 9 | 0.750 | 0.078 | 0.141 | 180.8% |
| 10 | 0.703 | 0.109 | 0.148 | 135.8% |
| 平均 | 0.727 | 0.086 | 0.141 | 166.2% |
%は配列方式とFindステートメントの比です。なるほど、配列を使うと速いです。でも、ちょっと待ってくださいよ。これ、200,000個のセルを探しているんです。200,000個ですよ。200,000個のセルを1つずつ見ていく力わざでも、約0.7秒しかかかっていないんです。これだけでも相当速いですよね。もちろんFindステートメントを使えばもっと速いです。0.14秒です。200,000個のセルですよ。確かに配列に入れると速いんですけど、そもそも今のExcelは、普通にセルを操作しても十分速いんです。
違うんです。配列を使って信じられないくらいマクロを速くするのは、こういう使い方じゃないんです。たとえば、次のようなケースで考えてください。A列には名前が10,000個入っています。これらの名前に該当する数値が、セル範囲D2:E6に入力されています。A列の名前に該当する数値を、VLOOKUP関数で調べて、それぞれB列に代入していきます。

ワークシート上のセルを1つずつ操作するのは遅いんでしたよね。

こんなときは一度配列に入れて、配列内を操作する方が速いはずです。

Sub Test4()
Dim i As Long
For i = 2 To 10001
Cells(i, 2) = WorksheetFunction.VLookup(Cells(i, 1), Range("D2:E6"), 2, False)
Next i
End Sub
Sub Test5()
Dim i As Long, B As Variant
B = Range("A1:A10001")
For i = 2 To 10001
Cells(i, 2) = WorksheetFunction.VLookup(B(i, 1), Range("D2:E6"), 2, False)
Next i
End Sub
| 回 | セル | 配列 | % |
|---|---|---|---|
| 1 | 15.313 | 12.687 | 82.9% |
| 2 | 13.313 | 12.844 | 96.5% |
| 3 | 12.453 | 11.828 | 95.0% |
| 4 | 12.640 | 11.500 | 91.0% |
| 5 | 13.594 | 12.250 | 90.1% |
| 6 | 12.672 | 12.656 | 99.9% |
| 7 | 12.156 | 11.984 | 98.6% |
| 8 | 11.609 | 11.375 | 98.0% |
| 9 | 11.547 | 11.219 | 97.2% |
| 10 | 12.156 | 13.000 | 106.9% |
| 平均 | 12.745 | 12.134 | 95.2% |
どうですか?速いですか?そんなに違わないでしょ。そうじゃないんです。遅いのは「セルの値を取得」するところじゃなく「セルに代入」するところなんです。上記のマクロは、いずれにしてもVLOOKUP関数の結果を、1つずつセルに代入しています。全部で10,000回です。この回数を減らすとマクロは劇的に速くなります。じゃ、どうしたらいいか。セルに代入する値をすべて配列に入れておいてその配列を1回だけ代入してやるんです。
Sub Test6()
Dim i As Long, B As Variant
ReDim B(9999, 0)
For i = 2 To 10001
B(i - 2, 0) = WorksheetFunction.VLookup(Cells(i, 1), Range("D2:E6"), 2, False)
Next i
Range("B2:B10001") = B
End Sub
| 回 | セル | 配列1 | 配列2 | % |
|---|---|---|---|---|
| 1 | 15.313 | 12.687 | 0.188 | 1.2% |
| 2 | 13.313 | 12.844 | 0.203 | 1.5% |
| 3 | 12.453 | 11.828 | 0.172 | 1.4% |
| 4 | 12.640 | 11.500 | 0.157 | 1.2% |
| 5 | 13.594 | 12.250 | 0.156 | 1.1% |
| 6 | 12.672 | 12.656 | 0.172 | 1.4% |
| 7 | 12.156 | 11.984 | 0.156 | 1.3% |
| 8 | 11.609 | 11.375 | 0.172 | 1.5% |
| 9 | 11.547 | 11.219 | 0.156 | 1.4% |
| 10 | 12.156 | 13.000 | 0.172 | 1.4% |
| 平均 | 12.745 | 12.134 | 0.170 | 1.3% |
劇的に速くなるでしょ。ポイントは配列に入れることじゃなく、配列をセルに代入することなんです。ただ、こうなると実務では難易度が上がります。上記の例では、データの件数が10,000件と決め打ちしました。実務では毎回分かりませんよね。代入するデータを一時的に格納するには、動的配列を使わなければなりません。しかも二次元配列です。今回のケースで、もしデータの件数が分からなかったら次のようになります。
Sub Test7()
Dim i As Long, B As Variant, n As Long
n = Cells(Rows.Count, 1).End(xlUp).Row
ReDim B(n - 2, 0)
For i = 2 To n
B(i - 2, 0) = WorksheetFunction.VLookup(Cells(i, 1), Range("D2:E6"), 2, False)
Next i
Range(Range("A2"), Cells(Rows.Count, 1).End(xlUp)).Offset(0, 1) = B
End Sub
どうです?難しいでしょ。動的な二次元配列をイメージするのも難しいですし、代入するセル範囲(ここではB列)を自動的に特定するのも難しいです。もちろん実際の実務では、さらに複雑になって難易度があがります。つまり、誰にでもできる簡単なマクロではないんです。教えて、誰もがすぐできるようになるわけじゃないですし、少なくともネットのコードを意味も分からずコピペしているレベルでは、おそらく無理でしょうね。そんなときは、潔くあきらめてくださいな。