VBAプログラム実行処理時間を短縮する方法とは?

このQ&Aのポイント
  • プログラム作成で処理時間が25秒程かかってしまった場合、処理速度を上げる方法はないでしょうか?具体的な改善方法についてお教えください。
  • 処理内容は、指定されたCSVファイルを自動で読み込むために作成されたプログラムです。処理が遅い原因や改善点について教えてください。
  • 動作環境はWindows10(64bit)およびExcel2016(32bit)です。処理時間を短縮するために必要な環境や設定の変更についてもお教えください。
回答を見る
  • ベストアンサー

VBA プログラム実行処理時間について

大変、お世話になっております。 以下のプログラムを作成しました所、処理時間が25、6秒程かかりました。 プログラムを訂正し、処理時間の速度を上げる方法はないでしょうか? 宜しくおねがいします。 ※処理は、添付画像の「CSV読込」ボタンで、カンマ区切りの「test.CSV」ファイルを自動で「CSV⇒Collectionサンプル.xlsm」の「CSV読込」Sheetへ流し込みます。また、G2セルのキー(果物)データをKEYにしてヒットした小計値をH2から下方向へヒット数分転記します。 <動作環境> Windows10(64bit)、Excel2016(32bit) CPU:Intel Core i7 7500U @ 2.70GHz Kaby Lake-U/Y 14nm Technology RAM:16.0GB <VBA-標準モジュール> Option Explicit Sub CSV読込_ボタン() Dim file As String: file = ThisWorkbook.Path & "\test.csv" 'CSVファイル指定 Dim Items As New Collection 'コレクションを生成 Dim Start, Finish, TotalTime As Single '処理時間 Dim i, j As Long Application.ScreenUpdating = False Application.DisplayAlerts = False Call DataClearFunc(1) Call ClearImmediate Start = Timer() ' 処理開始時刻を設定します。 Open file For Input As #1 'CSVファイルを開く Do Until EOF(1) '最終行までループ Dim buf As String: Line Input #1, buf '読み込んだデータを1行ずつみていく Dim tmp As Variant: tmp = Split(buf, ",") 'カンマで分割 With New Class1 'インスタンスの生成 .Name = CStr(tmp(0)) '果物 .Price = CInt(tmp(1)) '単価 .Number = CInt(tmp(2)) '個数 Items.Add .Self 'コレクションに追加 End With Loop Close #1 'CSVファイルを閉じる Dim item As Class1 'ループ用の変数 With ActiveSheet i = 2: j = 2 .Range("A1").Value = "果物": .Range("B1").Value = "単価": .Range("C1").Value = "個数": _ .Range("D1").Value = "小計": .Range("G1").Value = "キー(果物)": .Range("H1").Value = "小計" For Each item In Items 'コレクション内をループ ' Debug.Print item.Name, item.Price, item.Number, item.Sale 'プロパティを取得 .Range("A" & i).Value = item.Name: .Range("B" & i).Value = item.Price: _ .Range("C" & i).Value = item.Number: .Range("D" & i).Value = item.Sale If item.Name = .Range("G2").Value Then .Range("H" & j).Value = item.Sale: j = j + 1 'キーで要素取得・取出し i = i + 1 Next End With Debug.Print Items(3).Name '要素取出し Application.DisplayAlerts = True Application.ScreenUpdating = True Finish = Timer() ' 処理終了時刻を設定します。 TotalTime = Finish - Start ' 実際の処理時間を計算します。 MsgBox "処理時間は " & TotalTime & " 秒でした。" End Sub Sub データクリア_ボタン() ' Call ClearImmediate Call DataClearFunc(1) ActiveSheet.Range("A2").Select End Sub Sub DataClearFunc(Key As Long) Dim i, j, MaxRow As Long With ActiveSheet MaxRow = .Range("A2").End(xlDown).Row If Key Then .Range("A2" & ":D" & MaxRow).Clear: .Range("H2" & ":H" & MaxRow).Clear Else .Range("A2" & ":D" & MaxRow).Clear End If End With End Sub Public Sub ClearImmediate() Dim i As Long Dim CP, wd As VBIDE.Window With Application.VBE Set CP = .ActiveWindow Set wd = .Windows("イミディエイト") .Windows("イミディエイト").Visible = True ' CP.SetFocus Application.SendKeys "^g", False Application.SendKeys "^a", False Application.SendKeys "{Del}", False End With End Sub <VBAークラスモジュール> Option Explicit Public Name As String '果物(Name)プロパティ Public Price As Integer '単価(Price)プロパティ Public Number As Integer '個数(Number)プロパティ 'Saleプロパティは取得のみ Property Get Sale() As Integer Sale = Price * Number '単価×個数の値を使う End Property Public Property Get Self() As Class1 Set Self = Me End Property

質問者が選んだベストアンサー

  • ベストアンサー
  • f272
  • ベストアンサー率46% (7995/17085)
回答No.2

私が適当なcsvファイルを作って実行させたところ3.87秒かかった。 なお'Call ClearImmediateはコメントにした。 ----------------------------------------- 次に Start = Timer() ' 処理開始時刻を設定します。 から Finish = Timer() ' 処理終了時刻を設定します。 の間を,以下のように書き換えたら0.28秒になった。 ----------------------------------------- Open file For Input As #1 'CSVファイルを開く n = 0 Do Until EOF(1) '最終行までループ Dim buf As String: Line Input #1, buf '読み込んだデータを1行ずつみていく n = n + 1 Dim tmp As Variant: tmp = Split(buf, ",") 'カンマで分割 With New Class1 'インスタンスの生成 .Name = CStr(tmp(0)) '果物 .Price = CInt(tmp(1)) '単価 .Number = CInt(tmp(2)) '個数 Items.Add .Self 'コレクションに追加 End With Loop Close #1 'CSVファイルを閉じる Dim item As Class1 'ループ用の変数 With ActiveSheet i = 1: j = 1 .Range("A1").Value = "果物": .Range("B1").Value = "単価": .Range("C1").Value = "個数": _ .Range("D1").Value = "小計": .Range("G1").Value = "キー(果物)": .Range("H1").Value = "小計" ReDim aa(1 To n + 1, 1 To 4) ReDim bb(1 To n, 1 To 1) Dim Key As String Key = .Range("G2").Value For Each item In Items 'コレクション内をループ ' Debug.Print item.Name, item.Price, item.Number, item.Sale 'プロパティを取得 aa(i, 1) = item.Name: aa(i, 2) = item.Price: _ aa(i, 3) = item.Number: aa(i, 4) = item.Sale If item.Name = Key Then bb(j, 1) = item.Sale: j = j + 1 'キーで要素取得・取出し i = i + 1 Next .Range("A2").Resize(n, 4) = aa .Range("H2").Resize(j, 1) = bb End With Debug.Print Items(3).Name '要素取出し Application.DisplayAlerts = True Application.ScreenUpdating = True ----------------------------------------- さらに Start = Timer() ' 処理開始時刻を設定します。 から Finish = Timer() ' 処理終了時刻を設定します。 の間を,以下のように書き換えたら0.22秒になった。 ----------------------------------------- Open file For Input As #1 'CSVファイルを開く Dim buf As String Dim Key As String Dim tmp As Variant n = 0 Do Until EOF(1) '最終行までループ Line Input #1, buf '読み込んだデータを1行ずつみていく n = n + 1 Loop ReDim aa(1 To n, 1 To 4) ReDim bb(1 To n, 1 To 1) Key = ActiveSheet.Range("G2").Value i = 1: j = 1 Seek #1, 1 Do Until EOF(1) '最終行までループ Line Input #1, buf '読み込んだデータを1行ずつみていく tmp = Split(buf, ",") 'カンマで分割 aa(i, 1) = CStr(tmp(0)) '果物 aa(i, 2) = CInt(tmp(1)) '単価 aa(i, 3) = CInt(tmp(2)) '個数 aa(i, 4) = aa(i, 2) * aa(i, 3) If aa(i, 1) = Key Then bb(j, 1) = aa(i, 4): j = j + 1 'キーで要素取得・取出し i = i + 1 Loop Close #1 'CSVファイルを閉じる With ActiveSheet .Range("A1").Value = "果物": .Range("B1").Value = "単価": .Range("C1").Value = "個数": _ .Range("D1").Value = "小計": .Range("G1").Value = "キー(果物)": .Range("H1").Value = "小計" .Range("A2").Resize(n, 4) = aa .Range("H2").Resize(j, 1) = bb End With Debug.Print aa(3, 1) '要素取出し Application.DisplayAlerts = True Application.ScreenUpdating = True

-tama-tama-
質問者

お礼

2次元配列を使うのですね! ソースコードまで付けて下さって、本当にありがとうございます。 5000件のCSVデータを処理した所、25秒以上かかっていましたが、Collection処理を追加しても0.5秒以内に収まりました。

その他の回答 (1)

  • kon555
  • ベストアンサー率52% (1738/3330)
回答No.1

 ザッと拝見したところ、ループ処理で毎回セルに値を入れているようなので、「値を配列に入れてからセルに代入する」で高速化できるのではないかと思います。 以下参考ページ http://officetanaka.net/excel/vba/speed/s11.htm https://excel-ubara.com/excelvba1/EXCELVBA414.html https://mmm-program.com/vba-fast-array/

-tama-tama-
質問者

お礼

やはり配列を使うのですね! ありがとうございます。 頑張ります。

関連するQ&A

  • エクセルVBAなぜ実行時エラーが?

    エクセル2000です。 DATAと名づけた表の値を変換し、最大値から端数をプラマイするマクロなのですが、途中で「実行時エラー91 オブジェクト変数またはWithブロック変数が設定されていません。」となってしまいます。 なぜ出るかわからないので別なBookに同じ名前のRange をつくり同様の表でためしたらエラーにならずちゃんと作動します。 本番用のBookでのみエラーがでます。なぜなのでしょうか? 実行時エラーのでる With Range("DATA").Find(mx, LookIn:=xlValues) .Value = .Value + dff ' End With を、Withブロックをつかわず Range("DATA").Find(mx, LookIn:=xlValues).Select で試しても本番のBookではエラーになります。ほんとに困っています。 Sub 調整() Dim r As Double Dim c As Range Dim dff As Integer, mx As Long r = 25000 / Range("初期").Value With Sheets("内訳") Range("DATA").Value = .Range("F57:L73").Value '初期値複写 'MsgBox "初期値転写完了" For Each c In Range("DATA") If c.Value <> "" Then c.Value = Application.WorksheetFunction.Round(c.Value * r, -1) End If Next 'MsgBox "初期変換完了" dff = 25000 - Range("変換後") If dff <> 0 Then 'MsgBox dff mx = Application.WorksheetFunction.Max(Range("DATA")) 'MsgBox mx With Range("DATA").Find(mx, LookIn:=xlValues) .Value = .Value + dff 'ここで実行時エラー! End With End If End With End Sub

  • VBA マクロ処理時間の短縮について

    下記のコードを作りましたが、マクロを実行すると砂時計マークが表示されて、処理が終了するまでに30秒くらいかかります。 コードを変更して、マクロ処理時間を短縮する事はできないでしょうか? Sub A列のコピー() Dim rw2 As Long Dim rw1 As Long Dim newdate As Date With Worksheets("sheet1") rw2 = .cells(.Rows.Count, "c").End(xlUp).Row newdate = .Range("c" & rw2).value For rw1 = rw2 - 1 To 1 Step -1 If .Range("c" & rw1).value <> newdate Then Exit For Next rw1 .Range(.cells(rw1 + 1, 1), .cells(rw2, 1)).Copy Worksheets("sheet2").Range("v6").PasteSpecial xlValue If rw1 + 26 <= rw2 Then .Range(.cells(rw1 + 26, 1), .cells(rw2, 1)).Copy Worksheets("sheet2").Range("v40").PasteSpecial xlValue Application.CutCopyMode = False End If Application.CutCopyMode = False End With End Sub 各セルは、6000行くらいまで表示されています。  よろしくお願いします。

  • エクセルVBAでTransposeの不思議

    MS Officeのエクセル2000です。 下記Sub test01はRange("A1:I1")に文字列を入力し、一旦配列に取り込んでからワークシートに貼り付けるものです。 試験用のコードですので意味はありません。 このコードで255文字まではまったく問題はありません。 ところが、256文字以上の場合、横に貼り付けは問題ないのですが、 Transposeで縦に変換すると型が一致しません。(Error 13)となります。 どうしてでしょうか? 試行錯誤の結果、Sub test02のように一旦横に貼ったデータをコピーしてTransposeして貼り付けるのは大丈夫のようですので不思議でしょがありません。 またこの方法は列数256より要素が多い配列には使えないので解決策にはなりません。 ご教示くださいませ。 Sub test01() Dim myAr As Variant Dim i As Integer, n As Integer n = 256 '文字数 With ActiveSheet .UsedRange.ClearContents For i = 1 To 9 .Cells(1, i).Value = Application.Rept(Left(.Cells(1, i).Address(0, 0), 1), n) Next myAr = .Range("A1:I1").Value .Range("A3").Resize(, UBound(myAr, 2)).Value = myAr .Range("A5").Resize(UBound(myAr, 2)).Value = Application.Transpose(myAr) '256文字の場合エラー End With End Sub Sub test02() Dim myAr As Variant Dim i As Integer, n As Integer n = 256 '文字数 With ActiveSheet .UsedRange.ClearContents For i = 1 To 9 .Cells(1, i).Value = Application.Rept(Left(.Cells(1, i).Address(0, 0), 1), n) Next myAr = .Range("A1:I1").Value .Range("A3").Resize(, UBound(myAr, 2)).Value = myAr .Range("A3").Resize(, UBound(myAr, 2)).Copy .Range("A5").PasteSpecial Paste:=xlValues, Transpose:=True '256文字の場合もOK Application.CutCopyMode = False End With End Sub

  • VBA マクロ実行にてエラーが出ますが、原因を教えてください

    下記コードを実行すると、myCell.Selectのところで 実行時エラー’91’ オブジェクト変数またはWithブロック変数が設定されていません。 というエラーが出るのですが、どうすれば対策出来るのでしょうか? Sub test() Dim i As Long Dim myCell As Range With Range("A1").CurrentRegion For i = 2 To .Rows.Count Step 2 If i = 2 Then Set myCell = .Rows(i) Else Set myCell = Application.Union(myCell, .Rows(i)) End If Next i End With myCell.Select End Sub

  • 2つのVBAを組み合わせる方法

    お世話になります、2つのVBAを組み合わせる方法で迷っています。 1つ目が Private Sub Worksheet_Change(ByVal Target As Range) Dim i As Long, k As Long, myNum As Long If Intersect(Target, Range("C1,B9:B39")) Is Nothing Or Target.Count > 1 Then Exit Sub Application.EnableEvents = False With Target If .Column = 3 Then myNum = WorksheetFunction.Max(Range("B9:B39")) If IsDate(.Value) Then For i = 9 To 39 If Cells(i, "A").Value = "" Then Cells(i, "B").Value = "" Else Cells(i, "B") = myNum + i - 8 End If Next i End If Else i = .Row If .Value = "" Then Range(Cells(i + 1, "B"), Cells(39, "B")).ClearContents Else For k = i + 1 To 39 If Cells(k, "A").Value = "" Then Cells(k, "B").Value = "" Else Cells(k, "B") = Cells(k - 1, "B") + 1 End If Next k End If End If End With Application.EnableEvents = True End Sub です。 2つめが Private Sub Worksheet_Change(ByVal Target As Range)  Application.EnableEvents = True If Intersect(Target, Range("R8:R38")) Is Nothing Then Exit Sub Application.EnableEvents = False Range(Cells(Target.Row, 18), Cells(39, 18)).Value = Target.Value Application.EnableEvents = True End Sub です。2つのPrivate Sub Worksheet_Change(ByVal Target As Range)イベントのVBAですが、どのようにして組み合わせれば良いのでしょうか?

  • VBAの処理を軽くしたい

    各行ごとに5回セルを参照した結果を吐き出すプログラムを作成しています。 200行前後の処理が必要なのですが、現在のコードではあまりにも処理が重くなっています。 とにかく処理を軽くしたいのですが、どのような方法が考えられるでしょうか? 初心者なのでコードも含めてご教示頂けたら幸いです。 Sub test(b, c, d) Dim i1, i2 As Long Dim A1 As String  For i1=0 To Cells(Rows.Count, 1).End(xlUp).Row For i2 = 0 To 5 If Range(b).offset(i1, i2).Value = "x1" Or Range(c).offset(i1, i2).Value = "x1" Then A1 = "XXX" ElseIf Range(b).offset(i1, i2).Value = Range(c).offset(i1, i2).Value Then A1 = "YYY" Else A1 = "ZZZ" End If Range(d).offset(i1, i2).Value = A1 Next i2 Next i1 End Sub

  • Excel VBA 実行時エラー'1004':

     どちらの処理がより高速であるのかを調べるため、以下の2つのVBAを試作致しました。 Sub Macroコピペ乱数() 'コピペ乱数 Sheets("Sheet4").Select Range("A1:A99999").Formula = "=RAND()" Columns("A").Value = Columns("A").Value Range("B1").Select End Sub 及び Sub Macro繰り返し乱数() '繰り返し乱数 Dim i As Long Randomize Sheets("Sheet4").Select For i = 1 To 99999 Range("A" & i).Value = Rnd Next i Range("B1").Select End Sub  処が、これらのVBAを実際に動作させ様としますと、どちらの場合においても「Microsoft Visual Basic」ダイアログボックスが開いて 「実行時エラー'1004': 'Range'メソッドは失敗しました:'_Global'オブジェクト」 と表示されてしまいます。  さりとて、 Sub Macroコピペ乱数() 'コピペ乱数 Sheets("Sheet4").Select ActiveSheet.Range("A1:A99999").Formula = "=RAND()" Columns("A").Value = Columns("A").Value Range("B1").Select End Sub 及び Sub Macro繰り返し乱数() '繰り返し乱数 Dim i As Long Randomize Sheets("Sheet4").Select For i = 1 To 99999 Range("A" & i).Value = Rnd Next i Range("B1").Select End Sub 或いは Sub Macro繰り返し乱数() '繰り返し乱数 Dim i As Long Randomize Sheets("Sheet4").Select For i = 1 To 99999 Range(Cells(i, 1)).Value = Rnd Next i Range("B1").Select End Sub 等としましても、今度は 「実行時エラー'1004': アプリケーション定義またはオブジェクト定義のエラーです。」 となってしまいます。  どの部分がどの様に悪いのでしょうか?  そして、どの様に修正すれば良いのでしょうか?  尚、使用しておりますExcelのバージョンはExcel2010です。

  • VBAの繰りかえし処理について

    workbook1(以下wb1)のB3に入力した県名を含む行を、 workbook2から取り出し、wb1のB7以降に表示させたいと思っています (ちなみに県名はwb2のC列に入っています) 同じ県名が含まれる行が多いので、それらを繰り返し処理で 全て書き出したいと思い、以下のマクロを作りました。 Sub macro3() Dim c Dim wb1 As Workbook Dim wb2 As Workbook Dim k As Integer Dim firstAddress As String Application.ScreenUpdating = False Set wb1 = ActiveWorkbook Set wb2 = Workbooks.Open("G:\zyouhousyori\inn100best_full.csv") Set c = cell.Find(What:=Range("B3").Value) With wb2.Worksheets(1).Range("A1:A100") If Not c Is Nothing Then firstAddress = c.Address Do Set c = cell.FindNext(c) For k = 0 To 10 .Range("C100").End(xlUp).Offset(1).Copy _ wb1.Worksheets("sheet1").Cells(7 + k, 2) Exit For ★Loop While Not c Is Nothing And _ c.Address <> firstAddress End If End With Application.ScreenUpdating = True wb2.Close False End Sub しかし、実行すると★マークのついた所でエラーになってしまいます (対応するDoがありません、と出ます) VBA初心者なので、どこがどう違うのかいまいちわかりません; アドバイスお願いします。

  • Excel VBA; 複数のループ処理

    ↓のようなコードがあります。 Dim i As Long, MaxRow1 As Long, MaxRow2 As Long MaxRow1 = Worksheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row MaxRow2 = Worksheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Row For i = 1 To MaxRow1 Worksheets("Sheet1").Cells(i, 1).Value = i . Next i For i = 1 To MaxRow2 Worksheets("Sheet2").Cells(i, 1).Value = i . Next i これらを最大行の多いシートに合わせて、パフォーマンスを良くしたいと思います。 同様の処理をシート1、シート2で実施しているので纏めて記述したいです。 シート1、シート2に対する処理をサブルーチンにする方法しかありませんか? どなたかお願いします。 If MaxRow1 >= MaxRow2 Then For i = 1 To MaxRow1 Else For i = 1 To MaxRow2 End If Worksheets("Sheet1").Cells(i, 1).Value = i Worksheets("Sheet2").Cells(i, 1).Value = i . Next i

  • エクセルVBAでブックを開くと処理が終わってしまう

    VBA初心者なのですが、VBAでエクセルブックを開くとVBAの処理が終わってしまいます。理由がわからないのでアドバイスをお願いします。なお、止まってしまう箇所にコメントを入れプログラムを下記しました。また、4000字以上質問できないためプログラムの途中までしか書かれていません。そのため、余分な宣言が多数ありますが無視してください。よろしくお願いいたします。 Option Base 1 Sub 健康診断の郵送() Dim kyoNum() As String Dim b_name As String Dim a_name() As Variant Dim b_address As String Dim a_address() As Variant Dim mailNum() As Variant Dim place() As String Dim banchi() As String Dim ken() As String Dim Adr As String Dim AdrLen As Integer Dim i, j, k, cnt, l, m As Integer Dim ChrCode As Integer Dim cell As Range Dim Book1 As String Dim wb As Workbook Dim Book1_Path As String Dim flag As Boolean 'セルのクリア ThisWorkbook.ActiveSheet.Cells.ClearComments 'セルのプロパティを設定をする With ThisWorkbook.ActiveSheet.Columns("A:B") .ShrinkToFit = True .NumberFormatLocal = "@" .ColumnWidth = 45 End With 'カレントディレクトリのチェンジ(Windows2000以降) CreateObject("WScript.Shell").CurrentDirectory = ThisWorkbook.Path '簡易名称Book1にする Book1 = "Book1.xlsx" 'パスを取得する Book1_Path = ThisWorkbook.Path & "\" & Book1 If Dir(Book1_Path) = "" Then MsgBox "Book1.xlsxファイルが存在しません。", vbExclamation End If '同名ブックのチェック For Each wb In Workbooks If wb.Name = Book1 Then MsgBox "健康診断の郵送.xlsmはBook1を開こうとしています" _ & vbCrLf & "Book1を閉じて再実行してください", vbExclamation Exit Sub End If Next wb Application.ScreenUpdating = False '画面の更新を止める Workbooks.Open Book1_Path '*****←ここで処理が終わってしまう***** 'ブック名を指定して非表示 Application.Windows("Book1.xlsx").Visible = False '後方検索でBook1.xlsxの入力済みセルの行数と列数を取得 With Workbooks("Book1.xlsx").ActiveSheet.UsedRange Book1_MaxRow = .Find("*", , xlValues, , xlByRows, xlPrevious).Row - 2 'データ入力済み行数取得 End With Application.ScreenUpdating = True Workbooks("Book1.xlsx").Activate j = 1 ReDim kyoNum(Book1_MaxRow) ReDim a_name(Book1_MaxRow) ReDim a_address(Book1_MaxRow) ReDim mailNum(Book1_MaxRow) ReDim ken(Book1_MaxRow) ReDim place(Book1_MaxRow) ReDim banchi(Book1_MaxRow)

専門家に質問してみよう