Mr.Tです、こんにちは。
元ネタは、http://blogs.msdn.com/vbteam/archive/2009/10/06/hidden-costs-matt-gertz.aspx
# ほとんど、ネタに乗っかるだけの話なんで恐縮なんですけどね。
VB6.0以前でも云われてきた話じゃないかと思ってたんですが、ループなどで、こういう処理を書いたとします。
Public MyList As List(Of String) = New List(Of String)
Sub Main()
Dim s As New System.Text.StringBuilder
For i As Integer = 0 To MyList.Count
s.Append(MyList(i))
Next
End Sub
このとき、MyList.Countは、ループのたびに評価される、というのが隠れたCostということみたいです。
で、ほんじゃどんだけ違うのだろうと、MSILコードを見るとこんな感じでした。
#Costがあるってなら、何か余計な処理&時間がかかる処理があるんだろうと思うので。
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// コード サイズ 48 (0x30)
.maxstack 3
.locals init ([0] class [mscorlib]System.Text.StringBuilder s,
[1] int32 i,
[2] int32 VB$t_i4$L0)
IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0005: stloc.0
IL_0006: ldc.i4.0
IL_0007: ldsfld class [mscorlib]System.Collections.Generic.List`1<string> Sample.Module1::MyList
IL_000c: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count()
IL_0011: stloc.2(※1)
IL_0012: stloc.1
IL_0013: br.s IL_002b
IL_0015: ldloc.0
IL_0016: ldsfld class [mscorlib]System.Collections.Generic.List`1<string> Sample.Module1::MyList
IL_001b: ldloc.1
IL_001c: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)
IL_0021: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0026: pop
IL_0027: ldloc.1
IL_0028: ldc.i4.1
IL_0029: add.ovf
IL_002a: stloc.1
IL_002b: ldloc.1
IL_002c: ldloc.2
IL_002d: ble.s IL_0015
IL_002f: ret
} // end of method Module1::Main
MyList.Coutを事前に求めておいたパターンで同じようにやってみました。
Public MyList As List(Of String) = New List(Of String)
Sub Main()
Dim s As New System.Text.StringBuilder
Dim count As Integer = MyList.Count
For i As Integer = 0 To count
s.Append(MyList(i))
Next
End Sub
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// コード サイズ 50 (0x32)
.maxstack 3
.locals init ([0] int32 count,
[1] class [mscorlib]System.Text.StringBuilder s,
[2] int32 i,
[3] int32 VB$t_i4$L0)
IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0005: stloc.1
IL_0006: ldsfld class [mscorlib]System.Collections.Generic.List`1<string> Sample.Module1::MyList
IL_000b: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count()
IL_0010: stloc.0(※2)
IL_0011: ldc.i4.0
IL_0012: ldloc.0
IL_0013: stloc.3
IL_0014: stloc.2
IL_0015: br.s IL_002d
IL_0017: ldloc.1
IL_0018: ldsfld class [mscorlib]System.Collections.Generic.List`1<string> Sample.Module1::MyList
IL_001d: ldloc.2
IL_001e: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)
IL_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0028: pop
IL_0029: ldloc.2
IL_002a: ldc.i4.1
IL_002b: add.ovf
IL_002c: stloc.2
IL_002d: ldloc.2
IL_002e: ldloc.3
IL_002f: ble.s IL_0017
IL_0031: ret
} // end of method Module1::Main
よくわからんのだけど、この※1と※2は、Countの戻り値を評価スタックとやらにPushしてるわけで、この値を比較対象と
してるんだと思ってるんですが、違うんですかね?
この時点では、もうどちらもint32の値として利用されているから、Costなんてなさそうな感じがします。
前段では、
IL_002d: ble.s IL_0015
後段では、
IL_002f: ble.s IL_0017
ここで、ループというか指定行に飛んでいくようになってます。つまり、これを見る限りは、Appendは毎回評価されている
けど、Countは毎回なんぞ評価されていないように見えます。
じゃあ、For Each使えばどうなのよ、ってことで、
Public MyList As List(Of String) = New List(Of String)
Sub Main()
Dim s As New System.Text.StringBuilder
For Each data As String In MyList
s.Append(data)
Next
End Sub
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// コード サイズ 61 (0x3d)
.maxstack 2
.locals init ([0] class [mscorlib]System.Text.StringBuilder s,
[1] string data,
[2] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string> VB$t_struct$L0)
IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0005: stloc.0
.try
{
IL_0006: ldsfld class [mscorlib]System.Collections.Generic.List`1<string> Sample.Module1::MyList
IL_000b: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
IL_0010: stloc.2
IL_0011: br.s IL_0023
IL_0013: ldloca.s VB$t_struct$L0
IL_0015: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
IL_001a: stloc.1
IL_001b: ldloc.0
IL_001c: ldloc.1
IL_001d: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0022: pop
IL_0023: ldloca.s VB$t_struct$L0
IL_0025: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
IL_002a: brtrue.s IL_0013
IL_002c: leave.s IL_003c
} // end .try
finally
{
IL_002e: ldloca.s VB$t_struct$L0
IL_0030: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>
IL_0036: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_003b: endfinally
} // end handler
IL_003c: ret
} // end of method Module1::Main
まあ、このヘンになってくると比較にもならん結果になるので、パスw
で、引用元の人は、こういうのは最悪だろって話をしてるんだけど、
Public MyLameObjectHeader As MyLameObject = New MyLameObject
Sub Main()
Dim s As New System.Text.StringBuilder
For i As Integer = 0 To MyLameObjectHeader.Count
s.Append(MyLameObjectHeader.Item(i))
Next
End Sub
Public Class MyLameObject
Public data As String
Public NextMLO As MyLameObject
Public Sub Insert(ByVal mlo As MyLameObject)
If NextMLO IsNot Nothing Then
NextMLO = mlo
Else
mlo.NextMLO = NextMLO.NextMLO
NextMLO.NextMLO = mlo
End If
End Sub
Public Function Count() As Integer
Dim ct As Integer = 0
Dim current As MyLameObject = Me
Do While current IsNot Nothing
ct += 1
current = current.NextMLO
Loop
Return ct
End Function
Public Function Item(ByVal index As Integer) As MyLameObject
If index >= Count() OrElse index < 0 Then Return Nothing
Dim current As MyLameObject = Me
For i As Integer = 0 To index
current = current.NextMLO
Next
Return current
End Function
End Class
MyLameObject.Countが毎回、ループで処理されるからひどいことになるよってことですな。せめてこうしろって話ですな。
(Mainの変更後)
Public MyLameObjectHeader As MyLameObject = New MyLameObject
Sub Main()
Dim s As New System.Text.StringBuilder
Dim c As Integer = MyLameObjectHeader.Count
For i As Integer = 0 To c
s.Append(MyLameObjectHeader.Item(i))
Next
End Sub
--Main部分 変更前
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// コード サイズ 48 (0x30)
.maxstack 3
.locals init ([0] class [mscorlib]System.Text.StringBuilder s,
[1] int32 i,
[2] int32 VB$t_i4$L0)
IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0005: stloc.0
IL_0006: ldc.i4.0
IL_0007: ldsfld class Sample.Module1/MyLameObject Sample.Module1::MyLameObjectHeader
IL_000c: callvirt instance int32 Sample.Module1/MyLameObject::Count()
IL_0011: stloc.2
IL_0012: stloc.1
IL_0013: br.s IL_002b
IL_0015: ldloc.0
IL_0016: ldsfld class Sample.Module1/MyLameObject Sample.Module1::MyLameObjectHeader
IL_001b: ldloc.1
IL_001c: callvirt instance class Sample.Module1/MyLameObject Sample.Module1/MyLameObject::Item(int32)
IL_0021: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(object)
IL_0026: pop
IL_0027: ldloc.1
IL_0028: ldc.i4.1
IL_0029: add.ovf
IL_002a: stloc.1
IL_002b: ldloc.1
IL_002c: ldloc.2
IL_002d: ble.s IL_0015
IL_002f: ret
} // end of method Module1::Main
--Main部分 変更後
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// コード サイズ 50 (0x32)
.maxstack 3
.locals init ([0] int32 c,
[1] class [mscorlib]System.Text.StringBuilder s,
[2] int32 i,
[3] int32 VB$t_i4$L0)
IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0005: stloc.1
IL_0006: ldsfld class Sample.Module1/MyLameObject Sample.Module1::MyLameObjectHeader
IL_000b: callvirt instance int32 Sample.Module1/MyLameObject::Count()
IL_0010: stloc.0
IL_0011: ldc.i4.0
IL_0012: ldloc.0
IL_0013: stloc.3
IL_0014: stloc.2
IL_0015: br.s IL_002d
IL_0017: ldloc.1
IL_0018: ldsfld class Sample.Module1/MyLameObject Sample.Module1::MyLameObjectHeader
IL_001d: ldloc.2
IL_001e: callvirt instance class Sample.Module1/MyLameObject Sample.Module1/MyLameObject::Item(int32)
IL_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(object)
IL_0028: pop
IL_0029: ldloc.2
IL_002a: ldc.i4.1
IL_002b: add.ovf
IL_002c: stloc.2
IL_002d: ldloc.2
IL_002e: ldloc.3
IL_002f: ble.s IL_0017
IL_0031: ret
} // end of method Module1::Main
クラス部分は変更してないから、同じだし。
それ以外だと、やっぱりContの部分なんですが・・・これって、やっぱりループごとに評価されているわけじゃないように
見えるんですが・・・
サルでもわかる説明とかないですかねw