Mr.Tの場所

特攻野郎Aチームじゃないよー

ホーム 連絡をする 同期する ( RSS 2.0 ) Login
投稿数  253  : 記事  0  : コメント  3733  : トラックバック  52

ニュース

  • 性別:男
  • 猫1:まる
  • 猫2:もろ
  • 猫3:にゃん左部郎
  • タバコ:男は黙ってJPS
[わんくま同盟] C#, VB.NET 掲示板

書庫

日記カテゴリ

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

投稿日時 : 2009年10月7日 11:23

コメント

# re: [VB.NET]隠れたCOSTという話 2009/10/07 15:48 はつね
コンパイラが偉くなってくるとコード文法上の解釈では判断できなく例かと。

# re: [VB.NET]隠れたCOSTという話 2009/10/07 18:52 antsk
VBの言語仕様によるものですね。
http://msdn.microsoft.com/ja-jp/library/cc437876(VS.71).aspx
> Visual Basic は、start、end、step という反復値をループの開始前に 1 回だけ評価します。
> ステートメント ブロックによって end または step が変更されても、ループの繰り返しには影響しません。

C系の for (int i = 0; i < list.Count; i++) ステートメントは
2番目の式が「終了条件」なので、ふつうはループごとに条件を評価するコードが生成されます。

ものすごくコンパイラががんばれば、list.Countがループ中に変化しないことを検出して
最適化することもあるのかもしれませんが……。


# re: [VB.NET]隠れたCOSTという話 2009/10/08 7:49 黒龍
この例だとあれですがプロパティアクセスはフィールドっぽく見えて結局のところメソッド呼び出しなので癖付けといたほうがいいとは思います。
たまにとんでもなく遅いプロパティアクセスあったりプロパティを掘っていくと重かったりなので。(インデクサも同様)

# re: [VB.NET]隠れたCOSTという話 2009/10/08 8:11 Mr.T
確かに、とんでもなく遅いプロパティなら、効果はあるのかなとは、思います。
が、適当なサンプルを考えるのも面倒w

今回の場合では単なるループだけど、ここでDB
アクセスとかしてたりすると、アクセスするたび
毎回DBアクセスいくことになるとかひどいですね。

でも、単純ループならコンパイラが賢いので
問題ねーよというところでしょうかね。


Post Feedback

タイトル
名前
Url:
コメント