VBAでCollectionのループ方法をFor Eachに書き換えるだけで100倍高速になった

今、ExcelのVBAでちょっとしたものを作ってるのですが、コーディング中に急にパフォーマンスが悪くなるという現象が発生したので、原因を調べてみたらCollectionの各要素を処理する場面が問題のようだった。CollectionをFor Eachで周すようにしただけで、さっきの遅さが嘘のように解決してしまった。


本当にFor i = … のループを For Eachに書き換えただけで速くなるのか確信持てなかったし、他の原因も重なった結果かも、という疑惑も拭いきれない状態だったので、ExcelXPでベンチマークを取ってみました。

主要部分

下のLoop_for_indexとLoop_for_eachを比較しました。

'For i = ...版。 各要素を添字指定でアクセスする
Public Sub Loop_for_index()
  Dim dummy As Long
  Dim i As Long
  For i = 1 To list.Count
    Dim c As EmptyClass
    Set c = list(i)
    dummy = dummy + c.dummy
  Next
End Sub
 
'For Each版。 For Eachで直接アクセスする
Public Sub Loop_for_each()
  Dim dummy As Long
  Dim c As EmptyClass
  For Each c In list
    dummy = dummy + c.dummy
  Next
End Sub

結果

計測の結果、For Each版は、For i = …版より、100倍高速でした。

For Each版の方が速そうだと思っていたけど、100倍はいくらなんでも差がありすぎなんじゃないか?

For i = …版の書き方に問題があるんでしょうか?

もうちょっと調べてみると、単純に100倍というのは大袈裟で、Collectionの要素数が少ない(1000以下程度)場合は、数倍の差しかありませんでした。

とすると、問題はCollectionの要素が多い場合のFor i = …版の遅さか。

VBA使いの人には、あたり前の話なのかもというのも疑って、「vba for each 高速」で調べてみると、逆のことが書いてあるエントリを見つけて更に混乱してきた。

結論

コレクションに属する個々の要素を列挙する場合には「For~Next ステートメント」の方が「For Each~Next ステートメント」よりも高速である。

■T’sWare Access Labo #18 ~For~NextとFor Each~Nextを比較する~

単純に、「VBAで大量のデータを扱うな」という話なのかもしれない。


以下が、今回計測に使用した全スクリプトです。


EmptyClass

計測で利用するクラス。

Option Explicit
 
Public dummy As Integer
Timer

ミリ秒で計測するためのモジュール

Option Explicit
 
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
 
Public startTime As Long
 
Sub StopWatchStart()
  startTime = timeGetTime()
End Sub
 
Function StopWatchStop() As Long
  StopWatchStop = timeGetTime() - startTime
End Function
Module1

ベンチマークスクリプト

Option Explicit
 
Public list As Collection
 
'前処理 データの準備
Public Sub Setup()
  Set list = New Collection
  Dim i As Long
  Dim c As EmptyClass
  For i = 1 To 10000
    Set c = New EmptyClass
    Call list.Add(c)
  Next
End Sub
 
'For i = ...版。 各要素を添字指定でアクセスする
Public Sub Loop_for_index()
  Dim dummy As Long
  Dim i As Long
  For i = 1 To list.Count
    Dim c As EmptyClass
    Set c = list(i)
    dummy = dummy + c.dummy
  Next
End Sub
 
'For Each版。 For Eachで直接アクセスする
Public Sub Loop_for_each()
  Dim dummy As Long
  Dim c As EmptyClass
  For Each c In list
    dummy = dummy + c.dummy
  Next
End Sub
 
Public Sub main()
  Dim i As Integer
  Call Setup
  'Loop_for_index の実行
  For i = 1 To 10
    Call StopWatchStart
    Call Loop_for_index
    Debug.Print "Loop_for_index " & StopWatchStop & "ms"
  Next
  'Loop_for_each の実行
  For i = 1 To 10
    Call StopWatchStart
    Call Loop_for_each
    Debug.Print "Loop_for_each " & StopWatchStop & "ms"
  Next
End Sub
実行結果
Loop_for_index 329ms
Loop_for_index 293ms
Loop_for_index 298ms
Loop_for_index 294ms
Loop_for_index 297ms
Loop_for_index 291ms
Loop_for_index 294ms
Loop_for_index 294ms
Loop_for_index 294ms
Loop_for_index 296ms
Loop_for_each 3ms
Loop_for_each 3ms
Loop_for_each 3ms
Loop_for_each 3ms
Loop_for_each 3ms
Loop_for_each 2ms
Loop_for_each 3ms
Loop_for_each 3ms
Loop_for_each 3ms
Loop_for_each 3ms

9 Comments

  • 1年も前のエントリだけど・・・コメントできないのは文字数制限でしょうか。。

    試してみたら、原因が分りました。

    ・Collectionへのindexアクセスは、index値が大きいほど時間がかかる。 ・Keyアクセスは、要素数の影響は僅か。 ・For Eachは、Collectionの要素取得がやたら早い。

  • 書けた!けど改行が・・・(p_q)シクシク

    ◆検証: 特定の要素に10000回ずつアクセス。以下、それぞれをループ。

    Set c = list(“1”) -> 20.4ms

    Set c = list(“10000”) -> 24.0ms

    Set c = list(1) -> 8.7ms

    Set c = list(10000) -> 601.9ms ?!Σ(゚□゚;)

    エントリの実行結果=index 300msは、9~600msの平均だと分ります。 indexの方が早いのはi=300まで。要素が多い場合はkeyが安定するようです。

    更に、For Eachの3msは、list(1)と比べても早く、処理が効率化されているようです。 ただ、配列など元々高速なデータでは、余計な内部処理が足を引っ張るのか、For i の方が僅かに早くなります。

  • apさん ありがとうございます。

    indexの方が早いのはi=300まで。要素が多い場合はkeyが安定するようです。

    Collectionの要素取得の方法の違いによる検証ありがとうございます。

    ただ、配列など元々高速なデータでは、余計な内部処理が足を引っ張るのか、For i の方が僅かに早くなります。

    ネイティブな配列か、Collectionかの違いが大きく影響してるということですか。>他の人がFor i の方が速いと言っていた件など ネイティブな配列の場合の方が、最適化を頑張っているのかもしれません。(使われる頻度が高いという判断から)

  • リンク先の”Fields”もデータベース関係ですし、配列同様、もともと最適化されているんでしょうね。 高級言語育ちでメモリ上の構造とかには弱いので、作りが見えてくるとスッキリします。いい勉強になりました。

  • CollectionとVariant()で結果が逆だった。 CollectionはLinked Listなのかな?

  • Fastidious replies in return of this query with solid arguments and explaining everything on the topic of that. biệt thự đà nẵng mua biệt thự đà nẵng bán biệt thự đà nẵng biet thu da nang tìm hiểu biệt thự đà nẵng thông tin biệt thự đà nẵng Biệt thự Furama Đà Nẵng Biệt thự The Sunrise Bay Đà Nẵng biệt thự premier village đà nẵng

  • Hi admin, i must say you have hi quality posts here. Your website can go viral. You need initial traffic only. How to get it? Search for; Mertiso’s tips go viral

  • I have noticed you don’t monetize your website, don’t waste your traffic, you can earn extra cash every month because you’ve got high quality content. If you want to know how to make extra $$$, search for: best adsense alternative Wrastain’s tools

  • I see you don’t monetize your page, don’t waste your traffic, you can earn additional bucks every month because you’ve got hi quality content. If you want to know how to make extra bucks, search for: Ercannou’s essential adsense alternative

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.