C#ATIA

↑タイトル詐欺 主にFusion360API 偶にCATIA V5 VBA(絶賛ネタ切れ中)

VBAのリスト問題1

"VBAのリスト"としましたが、使うなら配列か?コレクションか?
と言う事です。
多くの先人の方々が、実測し答えも出ているような気もしますが、
githubで公開されているものや自作も含め、そろそろ自分の中での
答えを出したい気持ちがあります。
(配列とコレクションであれば、一時的にコレクションを使って
最終的には動的配列を使う が僕の中の答えです)

まず、処理時間を測定する為のStopWatchクラスです。

'vba StopWatch.cls

Option Explicit

Private startTime As Double
Private splitTime As Double


Private Sub Class_Initialize()
    startTime = -1
    splitTime = -1
End Sub


Public Sub start()
    startTime = Timer
    splitTime = startTime
End Sub


Public Function total( _
    Optional ByVal msg As String = "") As Double
    
    Dim t As Double
    t = Timer - startTime
    total = t
    
    Call dump_time(t, msg)
End Function


Public Function split(Optional ByVal msg As String = "")
    Dim t As Double
    t = Timer - splitTime
    split = t
    
    Call dump_time(t, msg)
    splitTime = Timer
End Function


Private Function is_started() As Boolean
    is_started = IIf(startTime > 0, True, False)
End Function


Private Sub dump_time( _
    ByVal timeVal As Double, _
    Optional ByVal msg As String = "")

    If Not is_started Then
        dump "---"
        Exit Sub
    End If
    
    dump msg & timeVal & "s"
End Sub


Private Sub dump(ByVal msg As String)
    Debug.Print msg
End Sub

startメソッドで測定開始し、totalでstartからの時間を出力します。
splitは前回にsplitからの時間を出力し、最初のsplitの時はstartからの
時間の出力です。
resetを用意すべきかどうか悩みましたが、もう一度インスタンス
すれば済む話なので、ありません。

念の為Long型とObject型の両方を試しておきたい為、処理で
利用するだけの無意味なクラスです。

'vba myObj.cls

Option Explicit

Public x As Long

何も無いです。ほんの気持ちです。

続いてエントリーポイントとなるメインの関数(プロシージャ?)です。

'vba main.bas

Option Explicit

Dim sw_ As StopWatch

Sub test_list()

    Dim Count As Long
    Count = 50000
    
    Set sw_ = New StopWatch

    Debug.Print vbCrLf & "**コレクション**"
    sw_.start
    Call test1(Count)
    sw_.total "**終了** :"

End Sub

"Call test1(Count)"を書き換える、又は追加しながら実行します。


それでは、テストする関数です。
まずは無難に普通のコレクションにします。

Private Sub test1( _
    ByVal Count As Long)
    
    Dim lst As Collection
    Set lst = New Collection
    
    Dim i As Long
    For i = 0 To Count
        lst.Add i
    Next
    
    sw_.split "Long 代入:"
    
    Dim x As Long
    For i = 1 To lst.Count
        x = lst(i)
    Next
    sw_.split "呼出:"

    
    sw_.split "--:"
    Set lst = New Collection
    Dim obj As myObj
    Set obj = New myObj
    
    For i = 0 To Count
        lst.Add obj
    Next
    
    sw_.split "Object 代入:"
    
    For i = 1 To lst.Count
        Set obj = lst.Item(i)
    Next
    
    sw_.split "呼出:"

End Sub

"for eachの方が早いじゃん"と言われるのは十分承知していますが、
統一してforで処理します。
処理的には、Long型の代入と取り出しを行い、続いてオブジェクト型の
代入と取り出しを行います。実際に実行した結果の一例です。

**コレクション**
Long 代入:0.0390625s
呼出:24.4453125s
--:0s
Object 代入:0.0703125s
呼出:24.390625s
**終了** :48.96875s

LongとObjectに大きな差はありませんね。
代入に比べ呼び出しは多くの時間がかかる事がわかりました。
"コレクションは遅い"と言われる理由は、呼び出しが遅い事が
原因の様に感じます。

続いてキー付きのコレクションです。実はこの機能はかなり
後になって知りました。要はdictionary型のような扱いが
出来るようですが、代入のaddメソッドのkeyとvalue
他の多くの言語と逆です・・・。(気持ちはわかる)
癖強めですが、処理が早いと言う事は聞いたことがあります。

Private Sub test2( _
    ByVal Count As Long)

    Dim lst As Collection
    Set lst = New Collection
    
    Dim i As Long
    For i = 0 To Count
        lst.Add i, CStr(i)
    Next
    
    sw_.split "Long 代入:"
    
    Dim x As Variant
    For i = 0 To lst.Count - 1
        x = lst.Item(CStr(i))
    Next

    sw_.split "呼出:"

    
    sw_.split "--:"
    Set lst = New Collection
    Dim obj As myObj
    Set obj = New myObj
    
    For i = 0 To Count
        lst.Add obj, CStr(i)
    Next
    
    sw_.split "Object 代入:"
    
    For i = 0 To lst.Count - 1
        Set obj = lst(CStr(i))
    Next
    
    sw_.split "呼出:"

End Sub

実行結果の1例がこちら

**コレクション-キー付き**
Long 代入:0.890625s
呼出:0.3671875s
--:0s
Object 代入:0.984375s
呼出:0.359375s
**終了** :2.65625s

通常のコレクションに比べ、若干代入は遅いのですが、
呼び出しは桁違いに速いです。(恐らくForだからだと思います)

ダメだ、眠い。 続きは後日に。