VBAはデータを扱うならば相当何でもできる万能選手ですが、一つ大きな欠点が…。
それは、速度。
何も気にせずにコードを書くと、超絶遅い処理となってしまいます。
これを回避するべく、色々と工夫するのも日々の業務効率改善に繋がります。
ちょっとした作業でも、自動処理に手間がかかると人はダレてしまい、集中力を削がれるものなので…。
自動化するならば、可能な限り最速、もしくは「状況によらず常に一定なベターな高速」である方が、業務効率改善はできるわけです。
■さて、IT業ではごく普通の自動処理の基本形となるVBAスクリプトですが
今回ご紹介するのは、テキストファイルを読み込み、EXCELシートに展開すると言う基本中の基本のコードです。
しかし…
10行、20行ならばともかく、これを1万行、10万行、100万行とこなしていくに従い、きちんと高速化を念頭にコードを書かなければ、軽く昼寝ができてしまう処理になってしまいます。
これを防ぐため、100万行の処理でも「目標3分以内にすべて反映できるコード」を目指しましょう。
■以下、ソースです。
普通にこういうソースをサラっと書けるようであれば、業務でも通用します。
気をつける点は
①テキストファイルのサイズが分からないので、メモリに無理させぬように行数×列数=要素数を意識し、保有しているPCのメモリと比較し、PCの暴走・フリーズを防ぎつつ、出来るだけ高速化できるようなメモリサイズを確保すること。
②できるだけEXCELに転記する回数を減らす事(1回でできるだけ大量にデータを貼り付ける事)
ですね。
①②の程ほどちょうどいい所を探れば、常識的には1000行~多くとも10000行でのcommit(書き込み)が重要となるでしょう。
※このソースの試験は10万行、56列(560万要素)と言うデータ量で行いましたが、比較的低速のマシンでも30秒以内に反映できました。
下記のソースでは、10000行単位でメモリをクリアしてるので、1行あたり10000列など滅茶苦茶なデータ構成でない限りは、まず暴走する懸念はないでしょう。
まず、1行あたり10000列ならば、データ構成を疑うべきです(10000列も扱えるほど人間は賢くない、せいぜい1000列以内)
意外と、SEはVBAできちんと処理効率化出来ない人が多いので、こういうテクニックで差をつけると「何故、アイツは早く帰れるんだ?」ってことになるかもしれません。
業務SEは、散々EXCELのお世話になってる割には、意外とEXCELの使い方が雑です。
私からすれば、ここを変えるだけで、2時間早く帰れると思いますけどね笑
業務SEは、自分の本来のスキル(exeファイルの開発能力)に拘り過ぎて、実は一番時間がかかっている調査・エビデンス作成部分のスキルが足りない人が圧倒的に多いんです。
そして、年収の違いの差は、実はここにありますね。
業務SEは、2時間早く仕事を終え、飲みにいきましょう。社会に貢献できますよ^^
自分自身の業務上、何が一番時間的にボトルネックとなっているのかを悟るべきです。
社会・世間はともかく、自分は「楽しましょうよ」
爆速処理して帰宅しましょう、遊びに行きましょう!
会社的に良い人材になれ、早く帰れて、自由時間もある。
もう、使うしかないわけですよ笑
もっともあんまりこういう技術が定番化すると、私が早く帰れなくなるので、最近身内に教える事もしてませんがね苦笑
早くできる事がバレると、それはそれで笑
さて、このソースの何がどういう理由で速いのでしょうか。
分かった方は「すばらしい!」ですね^^
--------------------------------------------
'変数宣言を必須とする
Option Explicit
Sub importTextFile()
'所謂おまじない。しかしVBA高速化の基本で「遅くなることはない」「爆速になることはある」⇒だからおまじない
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim DIR As String
DIR = "C:\Users\User\Documents\"
If "\" <> Right(DIR, 1) Then
DIR = DIR & "\"
End If
Const fName As String = "test.txt"
Const MaxLine As Long = 10000 - 1
Const otSheetNm As String = "Sheet1"
Dim buf1 As Variant
Dim buf2() As Variant
Dim maxArray1 As Long
Dim cnt As Long
Dim wRow As Long
Dim i As Long
'With句は単なる記述の簡略化ではない。参照エリアを都度調べずにすむ立派な高速化手段である(常識)
'With Thisworkbook指定は、マクロ動作中に人がwindowsをいじる場合も考慮した優秀な指定手段。
'↑この指定をすれば、アクティブウィンドウが切り替わってもVBAは、参照エリアに困らない。
'基本的に、アクティブウインドウに頼る発想は、VBA的な制約がない限りしないほうが良い
'出来ればWith Thisworkbook.Sheets(シートインデックorシート番号).Cellsまで刷るのが良い
'上記のWithまで領域指定し、Offsetで細かいセル操作をするのが最速である。
'ちなみに、withでcellsまで指定するのと、sheetsまでしか指定しないのでは、10~20%パフォーマンスが変わります。
'故に、できるだけcellsまでWith句で指定し、セル操作はcellsではなくOffsetで行うのがプロ。
'大量データを一気に扱いたい場合はCells.Resizeがコード的にも見やすく、しかも爆速
'地味ながら確実に効くWithでの参照先指定
With ThisWorkbook.Sheets(otSheetNm)
'当然ですよね?データの正確な転記は、文字列指定は基本中の基本+EXCELの勝手な型解釈を防ぎ「
'速度上の話で言えば「EXCELによる型解釈を@(文字列)指定にする事で、すっとばせる(安定の爆速化)
.Cells.NumberFormatLocal = "@"
Open DIR & fName For Input As #1
Line Input #1, buf1
buf1 = Split(buf1, vbTab)
maxArray1 = UBound(buf1)
'ReDimは確保領域サイズに応じて遅くなる。これはC言語のreallocと同じ。
'その為、ループ処理中にReDimを行わない様にするのはVBA高速化の影の主役。
ReDim buf2(MaxLine, maxArray1) As Variant
Close #1
Open DIR & fName For Input As #1
cnt = 0
wRow = 1
Do Until EOF(1)
Line Input #1, buf1
buf1 = Split(buf1, vbTab)
For i = 0 To maxArray1
buf2(cnt, i) = buf1(i)
Next i
cnt = cnt + 1
If MaxLine + 1 = cnt Then
.Cells(wRow, 1).Resize(MaxLine + 1, maxArray1 + 1) = buf2
wRow = wRow + MaxLine + 1
cnt = 0
End If
Loop
Close #1
'最後っ屁
If 0 < cnt Then
.Cells(wRow, 1).Resize(cnt, maxArray1 + 1) = buf2
End If
End With
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
■さて
今回はたいしたコメントも書かず、生々しいコードのみで遭えて書いてますが、どこら辺に高速化の工夫があるかご理解いただける方は、「そうだよね」と思う筈です。
また、ちょくちょくこういう情報を発信していきますね。
また、ご質問あればコメント欄までお願いします。
出来るだけ分かりやすく答えたいと思います。
では、また!