KEN's .NET

[特集3] 型変換の速度実験と考察

ホーム > KEN's .NET > [特集3] 型変換の速度実験と考察

ここではVB.NETの型変換についての速度実験と考察を書きます。

1.はじめに

CStr、CIntなどのような型変換関数は従来のVBでもおなじみですが、.NETでの型の追加、変更、全面的なオブジェクト指向対応にあわせて、 いくつかの型変換関数、キーワードが追加/変更されています。 型の追加、変更に伴う分でCStrやCIntの延長線上にあるものとして、以下のような関数があります。

CBool | CByte | CChar | CDate | CDec | CDbl | CInt | CLng | CObj | CShort | CSng | CStr

例えば、この中のCIntはVB.NETでInteger型が32ビットになったことで、従来のVBのCLngに該当します。 そういった違う部分もありますが、基本的には今まで通りに使うことができます。

CObjは各クラス、構造体からObject型に変換しますが、すべてのクラス、構造体はObject型から派生しており、 すべてのデータ型は同時にObject型ですので、この関数は通常は必要ないでしょう。しかし、逆に、Object型に ClassAクラスのオブジェクトが入ってるとして、このObject型をClassA型とみなしたいときは明示的に変換が必要です。 (Animalクラスからは派生したLionクラスがあるとき、 「a Lion is an Animal.」は成り立ちますが、「an Animal is a Lion.」は必ずしも成り立たないからです。)

これを実現する関数が、CType関数です。使用方法は以下のような感じになります。

Dim a As typename = CType(expression, typename)

第一引数に変換したい値/変数を指定し、第二引数に変換予定の型(通常の型、列挙型、クラス、構造体)を指定します。 これにより互換性のある型同士の変換はすべてOKです。とまぁ、これで話が終わってしまうとなんとも貧祖な特集になってしまいますが、 もう1つ新しい型変換のキーワードがあります。それはDirectCastキーワードです。 使い方はCTypeとまったく同じですが、以下の2点が異なります。
  • 厳密な型変換のみ可能。例えば、Object型の変数に入っている"123.1"(Double型)という値をInteger型に変換することはできない。(CTypeはできる)
  • その代わりCType関数よりも高速
ここまでは、ヘルプに載っている話です。じゃあ、これらの処理にはどれぐらいの速度差があるのでしょうか? また、CType(値, Integer)のような使い方の場合、CInt関数を使うのと同じになるはずです。このような場合に、 CInt関数と比較してどう違うのでしょうか?そこでやっと本題ですが、DirectCast、CType、CIntで同じ変換を行う処理を書き、 For〜Nextでループさせた時間を計って比較してみました。

2.DirectCast、CType、CIntの速度実験


それぞれを使って速度実験を行うわけですが、そこで使うコードを以下に掲載します。
   1:' vbc /t:exe  /out:CastTest.exe /r:System.dll /optimize+ /optionstrict+ CastTest.vb
   2:Imports System
   3:
   4:Public Class CastTest
   5:
   6:    Public Shared Function Main(ByVal CmdArgs() As String) As Integer
   7:        Dim i As Integer
   8:        Dim startTime As DateTime
   9:        Dim endTime   As DateTime
  10:        Dim result    As Integer
  11:        Dim o         As Object = 123
  12:        Dim max       As Integer = 100000000
  13:
  14:        Console.WriteLine("-----------------------------------------------------")
  15:        Console.WriteLine(" 型変換の速度実験 ")
  16:        Console.WriteLine(" o = " + CStr(o) + " をInteger型に変換")
  17:        Console.WriteLine("-----------------------------------------------------")
  18:
  19:        Console.WriteLine("EmptyLoop Start " + CStr(max))
  20:        startTime = DateTime.Now
  21:        For i = 1 To max 
  22:        Next
  23:        endTime = DateTime.Now
  24:        Console.WriteLine("EmptyLoop End Time=" + _ 
CStr((endTime.Subtract(startTime)).TotalMilliseconds))
  25:
  26:        Console.WriteLine("-----------------------------------------------------")
  27:        Console.WriteLine("DirectCast Start " + CStr(max))
  28:        Try
  29:            startTime = DateTime.Now
  30:            For i = 1 To max 
  31:                result = DirectCast(o, Integer)
  32:            Next
  33:            endTime = DateTime.Now
  34:            Console.WriteLine("DirectCast End Time=" + _ 
CStr((endTime.Subtract(startTime)).TotalMilliseconds))
  35:        Catch ex As InvalidCastException
  36:            Console.WriteLine("DirectCast End Time=エラーにより無効")
  37:        End Try
  38:        
  39:        Console.WriteLine("-----------------------------------------------------")
  40:        Console.WriteLine("CType Start " + CStr(max))
  41:        startTime = DateTime.Now
  42:        For i = 1 To max 
  43:            result = CType(o, Integer)
  44:        Next
  45:        endTime = DateTime.Now
  46:        Console.WriteLine("CType End Time=" + _ 
CStr((endTime.Subtract(startTime)).TotalMilliseconds))
  47:
  48:        Console.WriteLine("-----------------------------------------------------")
  49:        Console.WriteLine("CInt Start " + CStr(max))
  50:        startTime = DateTime.Now
  51:        For i = 1 To max 
  52:            result = CInt(o)
  53:        Next
  54:        endTime = DateTime.Now
  55:        Console.WriteLine("CInt End Time=" + _ 
CStr((endTime.Subtract(startTime)).TotalMilliseconds))
  56:
  57:        Return 0
  58:    End Function
  59:
  60:End Class
図1 DirectCast、CType、CIntの速度比較用コード

コードの11行目、12行目の赤色の部分を変更し、 型変換の対象データ「o」を「123」「123.1」の2パターン、 変換処理の繰り返し回数「max」を1億回と10億回の2パターン を用意し、組合せで計4パターンを実験しました。 コンソール画面に、空ループの実行時間、DirectCast、CType、CIntの実行時間の順で表示します。

開発&実行環境は、次のようになっています。
表1 実験環境
CPUメモリOS/IE.NET Framework バージョン
PentiumIII 1GHz384MBWindows2000(SP2+SRP1)、IE61.0.3705.209(SP1)

また、コンパイルオプションは、図1の1行目にあるとおりで、厳密な型指定の指示(/optionstrict+)、 最適化の指示(/optimize+)も行っています(最適化なしも一応試してみましたが、optimizeの有無であまり処理時間に差がありませんでした)。 では、実行結果を見てみましょう。(※ループ回数を10億回にした場合は、予想通り、単純に1億回の場合の10倍だったので省略します。)
-----------------------------------------------------
 型変換の速度実験 
 o = 123 をInteger型に変換
-----------------------------------------------------
EmptyLoop Start 100000000
EmptyLoop End Time=300.432
-----------------------------------------------------
DirectCast Start 100000000
DirectCast End Time=701.008
-----------------------------------------------------
CType Start 100000000
CType End Time=7300.4976
-----------------------------------------------------
CInt Start 100000000
CInt End Time=7390.6272
図2 o = 123, ループ回数=1億回 (時間の単位はms)

-----------------------------------------------------
 型変換の速度実験 
 o = 123.1 をInteger型に変換
-----------------------------------------------------
EmptyLoop Start 100000000
EmptyLoop End Time=400.576
-----------------------------------------------------
DirectCast Start 100000000
DirectCast End Time=エラーにより無効
-----------------------------------------------------
CType Start 100000000
CType End Time=16053.0832
-----------------------------------------------------
CInt Start 100000000
CInt End Time=16023.04
図3 o = 123.1, ループ回数=1億回 (時間の単位はms)

図2をご覧下さい。空ループにかかった時間はDirectCast、CType、CIntのどれを使ったとしても、ほぼ同じ時間であるはずです。 そう考えて各計測時間から300msを引くと、DirectCast 400ms、CType 7000ms、CInt 7090msということになり、 DirectCastに対して、CType、CIntは17倍から18倍ほどの時間がかかっていることになります。

次に図3をご覧下さい。前半で説明した通り、DirectCastでは「123.1」のようにInteger型でないもの(Double型)をInteger型に変換することはできません。 そのため、この実験では、123.1をInteger型にキャストしようとしてInvalidCastExceptionの例外が発生し、DirectCastの計測結果はありません。 また、CType、CIntの場合を見ると、図2の「o = 123」のときに比べて、 2倍強の時間がかかっていることがわかります。グラフにまとめると図4のようになります。

型変換の実行時間のグラフ
図4 型変換の実行時間のグラフ(1ブロック100ms)

さて、結論に行く前に、一つ気になることがあります。DirectCastとその他の違いははっきりしましたが、 CTypeとCIntの違いがあるのかないのかがはっきりしません。何度か実行時間を計測すると多少の変化があり、 それが誤差の範囲なのか微妙です。それぞれの内部ロジックがどうなっているのか気になります。 そこで.NET Framework SDKに付いているILDASM.exeというツールを使います。 これは.NET製実行ファイルを逆アセンブルし、IL(中間言語)コードを吐き出すツールです。ILはアセンブラのようなもので、 このコードを覗けば、VB.NETのコードが最終的にどういう形に変換されているのかわかります。 ILDASMは以下のように使います。

ILDASM CastTest.exe /out=CastTest.il

ILDASMで吐き出されたILコードから型変換部分を抜粋すると、以下のようになりました。
(DirectCast)
      IL_00d6:  unbox      [mscorlib]System.Int32
      IL_00db:  ldobj      [mscorlib]System.Int32

(CType)
    IL_0166:  call       int32 [Microsoft.VisualBasic]Microsoft.VisualBasic. [↓]
                               CompilerServices.IntegerType::FromObject(object)

(CInt)
    IL_01d1:  call       int32 [Microsoft.VisualBasic]Microsoft.VisualBasic. [↓]
                               CompilerServices.IntegerType::FromObject(object)
([↓]は表示の都合上折り返しているだけです。)

DirectCastのILコードはunboxでアンボクシング(値型と参照型の変換)だけを行っているようです。 CType、CIntの方は変換用のメソッドが呼び出されているようです。 その内容はともかくCType、CIntのILコードを見れば、知識がなくとも、同じコードになっていることが確認できると思います。 というわけで、CInt以外の例えばCStr関数などでも同じかどうかは同様の方法で確認しないと確実なことは言えませんが、 少なくともCInt(o)とCType(o , Integer)はILコードレベルでは等価なようです。

3.まとめ


  • DirectCastは使える状況が限られるが、CType関数、CInt関数よりも17〜18倍速い。
  • Double型からInteger型のような型変換がある場合は、Integer型からInteger型の変換の場合よりも2倍くらいの時間がかかる。
  • CInt(o)とCType(o, Integer)はコンパイル後は等価である。
以上のようなことがわかりました。今後は他の型についても少し実験してみたいです。

4.ソースコードとILコード

CastTest.vb
CastTest.il

ホーム > KEN's .NET > [特集3] 型変換の速度実験と考察

[e-mail] yone_ken00@hotmail.com