t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

【検証記事】VBAでWin32apiを使って変数のアドレス交換

先日、以下の記事を書いた。
thom.hateblo.jp

そのとき私は、VBAでメモリ番地の入れ替えはマシン語でも呼び出さないかぎり出来ないと思っていたのだが、ふつうにCopyMemoryで出来たようだ。

mmYYmmddさんがそれを実現する関数を作成してくれた。
https://mmyymmdd.hatenablog.com/entry/2015/08/05/235508mmyymmdd.hatenablog.com

さて、本当にByRef渡しするだけで番地入れ変えできているか、やはり自分で試さないと納得いかない性分なので、実際にやってみた。

'>|mmYYmmddさんのコードを引用|
Public Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _
     (ByRef Destination As Any, ByRef Source As Any, ByVal Length As LongPtr)

' Variant 変数のスワップ
Sub swapVariant2(ByRef x As Variant, ByRef y As Variant)
    Dim tmp As Variant
    CopyMemory tmp, x, 16
    CopyMemory x, y, 16
    CopyMemory y, tmp, 16
    CopyMemory tmp, Empty, 16   ' これが必要
End Sub
'|引用ここまで|<

Sub swapNormal(ByRef x, ByRef y)
    Dim tmp As Variant
    tmp = x
    x = y
    y = x
End Sub

Sub test()
    a = String(100000000, "★")
    b = String(100000000, "☆")
    
    Debug.Print "Take1:DataSwap"
    StartTime = Timer()
    For i = 1 To 10
        Call swapNormal(a, b)
    Next i
    Debug.Print Left(a, 1)
    Debug.Print Round(Timer() - StartTime, 7)
    
    Debug.Print "Take2:AddressSwap"
    StartTime = Timer()
    For i = 1 To 10
        Call swapVariant2(a, b)
    Next i
    Debug.Print Left(a, 1)
    Debug.Print Round(Timer() - StartTime, 7)
End Sub

testプロシージャの動作は、1億個の星が入った変数2個の中身を相互に入れ替えるというもの。
それを10回繰り返す処理である。
実行してみたところ、次の結果になった。

Take1:DataSwap
☆
 5.957031 
Take2:AddressSwap
☆
 0.0019531 

これは凄い!

データを直接入れ替えた場合は約6秒かかっている。
メモリアドレスの入れ替えで対応するswapVariant2関数では、約0.002秒で終わった。

検証結果

アドレス交換を利用した変数のSwapは、普通にデータを交換するより3000倍速い。

完成したswapVariant2関数を使う分には問題ないと思うが、CopyMemoryを単体使用するのは知識が無いと危ないとのことなので注意。

8/9追記

CopyMemory関数に渡すLength引数について、Officeの64bit版では24になるとのこと。
恐らく、ディレクティブで判定する必要があるので注意。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。