ラベル Faster の投稿を表示しています。 すべての投稿を表示
ラベル Faster の投稿を表示しています。 すべての投稿を表示

2009/05/18

String型の連結-VBA編

今回は「VB6.0やVBAだとこういう書き方もアリなんだー」という、個人的には目から鱗ネタです。下記のソース、(1)と(2)、どちらが速いか?というわけですね。

(1)
For i = 0 To lngMax
    strText1 = strText1 & "あいうえおかきくけこさしすせそ"
Next
(2)
For i = 0 To lngMax
    strText2 = strText2 & "あいうえおかきくけこさしすせそ"
    If Len(strText2) > 3000 Then strText1 = strText1 & strText2: strText2 = vbNullString
Next
If Len(strText2) Then strText1 = strText1 & strText2

(1)の方が一般的な書き方だと思いますが、この場合、(2)の方が圧倒的に速いのです。

結果

(1):343 ミリ秒

(2):16 ミリ秒

※Acceess2003で実験

VB6.0のString型は値型なので文字列を追加する度に、データの文字列をコピーしているわけですね。コピーするものが、長ければ長いほど、コピーの作業にコストがかかる。

ひるがえって(2)ではstrText2にちまちま文字列を追加して、 If Len(strText2) > 3000 のタイミングでstrText1に突っ込んで、strText2をカラッポにしてまた文字列をためていく。その方が、長い文字列をコピーする回数が激減して、処理が速くなるというわけなんですね(ちなみに.Netだとstring型は一応参照型なので、文字列をセットする度にコンストラクタが発生している様子でVB6.0/VBAどころではない負荷を、もはや「体感」として感じます。なのでStringBuilderのインデクサで対応)。

サンプルソース

フォームにtxtDATAという名前のテキストボックスを作って、そこにタイムを表示する。clsTimerMMクラスは「時間経過計測クラス‐VBA編」参照。

Dim clsTimerMM As clsTimerMM
Dim strTextMsg As String
'************************
Dim strMsg As String
Dim strText1 As String
Dim strText2 As String
Dim lngMax As Long
Dim i As Long
'************************
Set clsTimerMM = New clsTimerMM
strMsg = "あいうえおかきくけこさしすせそ"

lngMax = 10000
'************************
clsTimerMM.SetStartDate
For i = 0 To lngMax
    strText1 = strText1 & strMsg
Next
strTextMsg = "(1):" & clsTimerMM.ElapsedTime & vbCrLf
Me.txtText1.Value = strText1 & vbCrLf
strText1 = vbNullString
'************************
clsTimerMM.SetStartDate
For i = 0 To lngMax
    strText2 = strText2 & strMsg
    If Len(strText2) > 3000 Then strText1 = strText1 & strText2: strText2 = vbNullString
Next
If Len(strText2) Then strText1 = strText1 & strText2
strTextMsg = strTextMsg & "(2):" & clsTimerMM.ElapsedTime & vbCrLf
Me.txtText2.Value = strText1 & vbCrLf

Me.txtDATA.Value = strTextMsg
Set clsTimerMM = Nothing

2009/05/14

Function対決-VBA編

うーん、これも道理といば道理。さらに言えばC#を書きはじめるまで、ファンクションの返り値にこういう条件式をつっこむ、という書き方はしなかったんですけれど(なぜならVBで書くと、なんでも「=」で不気味だから)。

わざわざif文で評価してから返すよりも、ダイレクトに評価してしまった方が、当然処理は速いですものネ。…ま、Functionの中身はかなり短いロジックにならざるをえませんので需要があるかどうかはビミョーですが。とりあえず、速度対決。

(1)

Private Function IsTestTrue01(ByVal a As Long) As Boolean
If (a Mod 2 = 0) Then
    IsTestTrue01 = True
Else
    IsTestTrue01 = False
End If
End Function

(2)

Private Function IsTestTrue02(ByVal a As Long) As Boolean
IsTestTrue02 = (a Mod 2 = 0)
End Function

結果

(1):1756 ミリ秒

(2):1588 ミリ秒

※Access2003で実験

サンプルソース

フォームにtxtDATAという名前のテキストボックスを作って、そこにタイムを表示する。clsTimerMMクラスは「時間経過計測クラス‐VBA編」参照。

Dim clsTimerMM As clsTimerMM
Dim i As Long
Dim lngMax As Long
Dim strMsg As String

Set clsTimerMM = New clsTimerMM
lngMax = 10000000

'************************
clsTimerMM.SetStartDate
For i = 0 To lngMax
    Call IsTestTrue01(i)
Next
strMsg = "(1):" & clsTimerMM.ElapsedTime & vbCrLf
'************************
clsTimerMM.SetStartDate
For i = 0 To lngMax
    Call IsTestTrue02(i)
Next
strMsg = strMsg & "(2):" & clsTimerMM.ElapsedTime & vbCrLf

'************************
Me.txtDATA.Value = strMsg
Set clsTimerMM = Nothing

2009/05/11

空文字チェック対決-VBA編

VBAで空文字("")をチェックするときの速度1千万回対決。とりあえず、Nullは空文字ではないので除外。

結果

strData = "あいうえおかきくけこさしすせそ"
If strData = "" Then          :517 ミリ秒
If Len(strData) = 0 Then      :230 ミリ秒
If LenB(strData) = 0 Then     :234 ミリ秒

strData = "0123456789abcdefg"
If strData = "" Then          :516 ミリ秒
If Len(strData) = 0 Then      :270 ミリ秒
If LenB(strData) = 0 Then     :234 ミリ秒

※LenとLenBの速度差は、なんだか色々フクザツです。

strDataに「あいうえお」等が入っていると、Len/LenBの速度差の順位は、状況によってはひっくりかえったりしてマチマチ。「0123abcd」などの文字列のときは、何度やってもにLenBが早くなります。何故かはいまのところ不明。

サンプルソース

フォームにtxtDATAという名前のテキストボックスを作って、そこにタイムを表示する。clsTimerMMクラスは「時間経過計測クラス‐VBA編」参照。

Dim ClsTimerMM As ClsTimerMM
Dim strTextMsg As String
'************************
Dim strData As String
Dim strGet As String
Dim lngMax As Long
Dim i As Long
'************************
Set ClsTimerMM = New ClsTimerMM
strData = "あいうえおかきくけこさしすせそ"
'strData = "0123456789abcdefg"
strTextMsg = "strData:" & strData & vbCrLf
lngMax = 10000000
'************************
ClsTimerMM.SetStartDate
For i = 0 To lngMax
    If strData = "" Then
    End If
Next
strTextMsg = Left("If strData = """" Then" & String(30, " "), 30) & ":" & ClsTimerMM.ElapsedTime & vbCrLf
'************************
ClsTimerMM.SetStartDate
For i = 0 To lngMax
    If Len(strData) = 0 Then
    End If
Next
strTextMsg = strTextMsg & Left("If Len(strData) = 0 Then" & String(30, " "), 30) & ":" & ClsTimerMM.ElapsedTime & vbCrLf
'************************
ClsTimerMM.SetStartDate
For i = 0 To lngMax
    If LenB(strData) = 0 Then
    End If
Next
strTextMsg = strTextMsg & Left("If LenB(strData) = 0 Then" & String(30, " "), 30) & ":" & ClsTimerMM.ElapsedTime & vbCrLf
'************************
Me.txtDATA.Value = strTextMsg
Set ClsTimerMM = Nothing

$マークつきの関数について-MidとMid$関数対決

今回、PDFCreatorのソースをみていて気になったのがやたらと出てくる$マーク。UCase$とかOct$とかChr$とかMid$とかがものすごーく気になっていたわけですよ。ググってもひっかかりにくいし、VB6.0のヘルプは手元にないし、でもどうやらフツーに$記号ナシのUcaseやらMidと同じ動作をする。

悶々と々としていたのですが、ちょっと別のコトを調べていたら、スゴい記述をみつけてしまいました。英文サイトのFaster Visual Basic Programs。

マジでー?!

$マークつけた方が、処理早いの??? Σ(゚Д゚;≡;゚д゚)

Midの返り値はvariantなのに対して、Mid$の返り値はString型だから、という理由にはナットクしたものの、Mid$なんて隠し玉の存在を知りませんでした。$で文字列を返すというのは、Basicの名残っぽいですよね。

「….Netでも実験してみよー!」と一瞬、思ったものの、.Netに移行してから、Mid関数とかあんまり使わないかなぁ。(´・ω・`)

私の今の行動範囲で現実的にこの知識を生かせるのってむしろVBA?──というわけでAccess2003で実験してみました。


まずはMidとMid$対決の一千万回対決。

MID$の方がちょっと速いですね。

結果

MID :3277 ミリ秒

MID$:3011 ミリ秒

サンプルソース

フォームにtxtDATAという名前のテキストボックスを作って、そこにタイムを表示する。clsTimerMMクラスは「時間経過計測クラス‐VBA編」参照。

Dim ClsTimerMM As ClsTimerMM
Dim strTextMsg As String
'************************
Dim strData As String
Dim strGet As String
Dim lngMax As Long
Dim i As Long
'************************
Set ClsTimerMM = New ClsTimerMM
strData = "あいうえおかきくけこさしすせそ"
lngMax = 10000000
'************************
ClsTimerMM.SetStartDate
For i = 0 To lngMax
    strGet = Mid(strData, "5", 1)
Next
strTextMsg = "MID :" & ClsTimerMM.ElapsedTime & vbCrLf
'************************
ClsTimerMM.SetStartDate
For i = 0 To lngMax
    strGet = Mid$(strData, "5", 1)
Next
strTextMsg = strTextMsg & "MID$:" & ClsTimerMM.ElapsedTime & vbCrLf
'************************

Me.txtDATA.Value = strTextMsg
Set ClsTimerMM = Nothing

参考

同類な関数

Chr ChrB CurDir Dir Format Hex Input InputB LCase Left LeftB Mid MidB Oct Right RightB RTrim Space Str String Trim UCase

時間経過計測クラス‐VBA編

いろいろAccessで測定してみたくなったので、まずは測定クラスの作成。

VBA単独はではミリ秒を測れないようなので、API関数を利用。

ClsTimerMM

Option Compare Database
Option Explicit

'============================
' ミリ秒 で経過時間を計測
'============================

Private Declare Function timeGetTime Lib "winmm.dll" () As Long
Public StartTime As Variant

Public Sub SetStartDate()
StartTime = timeGetTime()
End Sub
Public Function ElapsedTime() As String
ElapsedTime = (timeGetTime() - StartTime) & " ミリ秒"
End Function

そしてオマケで、hh時間nn分ss秒での計測バージョン。

ClsTimer

Option Compare Database
Option Explicit
'============================
' hh時間nn分ss秒 で経過時間を計測
'============================

Private StartDate As Variant
Public Sub SetStartDate()
StartDate = Now
End Sub
Public Function ElapsedTime() As String
ElapsedTime = Format(Now - StartDate, "hh時間nn分ss秒")
End Function

2008/08/26

データ変換速度比較

VB.NET/.NetFramwork2で型変換の速度差、特色について気になりだしたので、実験してみました。

興味があった変換は、普段よく使う

  • Object→Int32への変換
  • String→Int32への変換

です。

実験したものはCtype、CInt、DirectCast、Convert.ToInt32、Int32.Parse、 Int32.TryParse。

実験結果としてはこんな感じです。【 】の中の数値はObjectなりStringなりのmyData変数を、それぞれのメソッドなりでの変換したものです。Int32.TryParseで【 0】となっているのは、変換できなかったということですね。

実験結果

(速度の早い順に並び替え)
Dim my_Data As Object = 12345
DirectCast :        【     12345】00:00:00.0004363
CInt :              【     12345】00:00:00.0023329 'CtypeとCInt、Convert.ToInt32は順位が入れ替わるときがあります。
CType :             【     12345】00:00:00.0023815
Convert.ToInt32 :   【     12345】00:00:00.0030900
Int32.Parse :       【     12345】00:00:00.0468925 'Int32.ParseとInt32.TryParseは順位が入れ替わるときがあります。
Int32.TryParse :    【     12345】00:00:00.0475714


Dim my_Data As Object = 12345.5
CInt :              【     12346】00:00:00.0039555
CType :             【     12346】00:00:00.0040038
Convert.ToInt32 :   【     12346】00:00:00.0052587
Int32.TryParse :    【         0】00:00:00.0631435


Dim my_Data As String = "12345"
Int32.Parse :       【     12345】00:00:00.0198393
Int32.TryParse :    【     12345】00:00:00.0206115
Convert.ToInt32 :   【     12345】00:00:00.0214960
CType :             【     12345】00:00:01.7970309
CInt :              【     12345】00:00:01.7990923


Dim my_Data As String = "12345.5"
Int32.TryParse :    【         0】00:00:00.0198050
CInt :              【     12346】00:00:01.8433437
CType :             【     12346】00:00:01.8474300

所感としては、Object型のときは、DirectCastを使うのが一番高速(但しDirectCastは丸め処理などは行わず、Int32に変換するのに小数などを引数に渡すとエラーを吐きます)。String型のときは、Int32.Parseが一番高速…、というところでしょうかね。現実問題としてはInt32.TryParseも、使い勝手がよさそうです。

サンプルソース

▲TextBox1に結果が出るわけです…。

※Int32.Parseの引数はもともとString型なので、Option StrictはOffにしないと走りません。

Imports System.Diagnostics


Public Class Form1
    Private Sub Button1_Click(ByVal sender As Object, _
                              ByVal e As System.EventArgs) _
                              Handles Button1.Click

        Dim sw As New Stopwatch
        Dim sb_Data As New System.Text.StringBuilder
        Dim bl_result As Boolean
        Dim my_Dict As New Dictionary(Of String, String)


        Dim my_Data As Object = 12345  '#---ここをあれこれ差し替えました。
        '                                   少数の値をセットしたりして
        '                                   エラーができるときは、
        '                                   型変換部分も
        '                                   コメントアウトします。
        Dim int_Data As Int32

        '----------------------------------
        sw.Reset()
        sw.Start()
        For i As Int32 = 0 To 100000
            int_Data = CType(my_Data, Int32)
        Next
        sw.Stop()
        my_Dict.Add("CType" & vbTab _
                    & int_Data.ToString, _
                    sw.Elapsed.ToString)
        '----------------------------------
        sw.Reset()
        sw.Start()
        For i As Int32 = 0 To 100000
            int_Data = CInt(my_Data)
        Next
        sw.Stop()
        my_Dict.Add("CInt" & vbTab _
                    & int_Data.ToString, _
                    sw.Elapsed.ToString)
        '----------------------------------
        sw.Reset()
        sw.Start()
        For i As Int32 = 0 To 100000
            int_Data = DirectCast(my_Data, Int32)
        Next
        sw.Stop()
        my_Dict.Add("DirectCast" & vbTab _
                    & int_Data.ToString, _
                    sw.Elapsed.ToString)
        '----------------------------------
        sw.Reset()
        sw.Start()
        For i As Int32 = 0 To 100000
            int_Data = Convert.ToInt32(my_Data)
        Next
        sw.Stop()
        my_Dict.Add("Convert.ToInt32" & vbTab _
                    & int_Data.ToString, _
                    sw.Elapsed.ToString)
        '----------------------------------
        sw.Reset()
        sw.Start()
        For i As Int32 = 0 To 100000
            int_Data = Int32.Parse(my_Data)
        Next
        sw.Stop()
        my_Dict.Add("Int32.Parse" & vbTab _
                    & int_Data.ToString, _
                    sw.Elapsed.ToString)
        '----------------------------------
        sw.Reset()
        sw.Start()
        For i As Int32 = 0 To 100000
            bl_result = Int32.TryParse(my_Data, _
                                       int_Data)
        Next
        sw.Stop()
        my_Dict.Add("Int32.TryParse" & vbTab _
                    & int_Data.ToString, _
                    sw.Elapsed.ToString)
        '----------------------------------

        Dim sorted As List(Of KeyValuePair(Of String, String)) _
                = sortByValue(my_Dict)

        sorted.Reverse()

        For Each kvp As KeyValuePair(Of String, String) In sorted

            Dim str_SPL() As String _
                = kvp.Key.Split(vbTab.ToCharArray)
            Dim str_Num As String _
            = Strings.StrDup(10, " ") & str_SPL(1)

            sb_Data.Append((str_SPL(0) & " :" _
              & Strings.StrDup(20, " ")).Substring(0, 20) _
              & "【" & (str_Num).Substring(str_Num.Length - 10, 10) & "】" _
              & kvp.Value & vbCrLf)

        Next

        Me.TextBox1.Text = sb_Data.ToString
        sb_Data.Remove(0, sb_Data.Length)
        sb_Data = Nothing

    End Sub


    Shared Function hikaku(Of TKey, TValue As IComparable(Of TValue))( _
      ByVal kvp1 As KeyValuePair(Of TKey, TValue), _
      ByVal kvp2 As KeyValuePair(Of TKey, TValue)) As Integer

        Return kvp2.Value.CompareTo(kvp1.Value)
    End Function


    Shared Function sortByValue(Of TKey, TValue As IComparable(Of TValue))( _
      ByVal dict As Dictionary(Of TKey, TValue)) _
      As List(Of KeyValuePair(Of TKey, TValue))

        Dim list As New List(Of KeyValuePair(Of TKey, TValue))(dict)

        'Valueの大きい順にソート
        list.Sort(AddressOf hikaku)
        Return list
    End Function
End Class