【问题标题】:Does C# Collection Initialization Syntax Avoid Default Initialization OverheadC# 集合初始化语法是否避免默认初始化开销
【发布时间】:2009-06-18 17:44:41
【问题描述】:

当你使用新的 C# 集合初始化语法时:

string[] sarray = new[] { "A", "B", "C", "D" };

编译器是否避免将每个数组槽初始化为默认值,还是相当于:

string[] sarray = new string[4];  // all slots initialized to null
sarray[0] = "A";
sarray[1] = "B";
sarray[2] = "C";
sarray[3] = "D";

【问题讨论】:

    标签: c# optimization compiler-construction c#-3.0


    【解决方案1】:

    编译器仍使用newarr IL 指令,因此 CLR 仍将初始化数组。

    集合初始化是只是编译器魔法 - CLR 对此一无所知,因此它仍会假定它必须进行健全性清除。

    但是,这应该非常非常快 - 它只是擦除内存。我怀疑在许多情况下这会产生很大的开销。

    【讨论】:

    • 有趣。我想知道这种数组初始化的“内存擦除”方法是否是结构不支持显式默认构造函数或成员初始化程序的原因之一。这会使数组初始化复杂化。
    • 是的,很多。事实上,IL do 中的结构体支持无参数构造函数,但它们只会在某些情况下被调用。
    • “我怀疑这在许多情况下会产生很大的开销”:在这种情况下尤其如此,因为您通常只对小型数组使用集合初始化语法...
    【解决方案2】:

    快速测试:

            string[] arr1 =
            {
                "A","B","C","D"
            };
            arr1.GetHashCode();
    
            string[] arr2 = new string[4];
            arr2[0] = "A";
            arr2[1] = "B";
            arr2[2] = "C";
            arr2[3] = "D";
    
            arr2.GetHashCode();
    

    在这个 IL 中的结果(注意,它们都是相同的)

      IL_0002:  newarr     [mscorlib]System.String
      IL_0007:  stloc.2
      IL_0008:  ldloc.2
      IL_0009:  ldc.i4.0
      IL_000a:  ldstr      "A"
      IL_000f:  stelem.ref
      IL_0010:  ldloc.2
      IL_0011:  ldc.i4.1
      IL_0012:  ldstr      "B"
      IL_0017:  stelem.ref
      IL_0018:  ldloc.2
      IL_0019:  ldc.i4.2
      IL_001a:  ldstr      "C"
      IL_001f:  stelem.ref
      IL_0020:  ldloc.2
      IL_0021:  ldc.i4.3
      IL_0022:  ldstr      "D"
      IL_0027:  stelem.ref
      IL_0028:  ldloc.2
      IL_0029:  stloc.0
      IL_002a:  ldloc.0
      IL_002b:  callvirt   instance int32 [mscorlib]System.Object::GetHashCode()
      IL_0030:  pop
      IL_0031:  ldc.i4.4
      IL_0032:  newarr     [mscorlib]System.String
      IL_0037:  stloc.1
      IL_0038:  ldloc.1
      IL_0039:  ldc.i4.0
      IL_003a:  ldstr      "A"
      IL_003f:  stelem.ref
      IL_0040:  ldloc.1
      IL_0041:  ldc.i4.1
      IL_0042:  ldstr      "B"
      IL_0047:  stelem.ref
      IL_0048:  ldloc.1
      IL_0049:  ldc.i4.2
      IL_004a:  ldstr      "C"
      IL_004f:  stelem.ref
      IL_0050:  ldloc.1
      IL_0051:  ldc.i4.3
      IL_0052:  ldstr      "D"
      IL_0057:  stelem.ref
      IL_0058:  ldloc.1
      IL_0059:  callvirt   instance int32 [mscorlib]System.Object::GetHashCode()
    

    【讨论】:

      【解决方案3】:

      我对使用您描述的语法实例化数组进行了简短测试,发现使用非默认值进行实例化比使用默认值进行实例化花费大约 2.2 倍。

      当我使用默认值进行切换和实例化时,所花费的时间大致相同。

      确实,当我查看反编译时,似乎发生的是数组被初始化,然后填充了任何非默认值。

      用非默认值实例化:

                  bool[] abPrimes = new[] { 
                      true, true
                  };
      0000007e  mov         edx,2 
      00000083  mov         ecx,79114A46h 
      00000088  call        FD3006F0 
      0000008d  mov         dword ptr [ebp-64h],eax 
      00000090  mov         eax,dword ptr [ebp-64h] 
      00000093  mov         dword ptr [ebp-54h],eax 
      00000096  mov         eax,dword ptr [ebp-54h] 
      00000099  cmp         dword ptr [eax+4],0 
      0000009d  ja          000000A4 
      0000009f  call        76A9A8DC 
      000000a4  mov         byte ptr [eax+8],1 
      000000a8  mov         eax,dword ptr [ebp-54h] 
      000000ab  cmp         dword ptr [eax+4],1 
      000000af  ja          000000B6 
      000000b1  call        76A9A8DC 
      000000b6  mov         byte ptr [eax+9],1 
      000000ba  mov         eax,dword ptr [ebp-54h] 
      000000bd  mov         dword ptr [ebp-40h],eax 
      

      使用默认值实例化:

      bool[] abPrimes2 = new[] { 
                    false, false
                  };
      000000c0  mov         edx,2 
      000000c5  mov         ecx,79114A46h 
      000000ca  call        FD3006F0 
      000000cf  mov         dword ptr [ebp-68h],eax 
      000000d2  mov         eax,dword ptr [ebp-68h] 
      000000d5  mov         dword ptr [ebp-54h],eax 
      000000d8  mov         eax,dword ptr [ebp-54h] 
      000000db  mov         dword ptr [ebp-5Ch],eax 
      

      【讨论】:

        【解决方案4】:

        不可能避免将每个数组槽初始化为默认值,至少在 IL 级别。

        String 是 CLASS,而不是结构。

        这意味着 A、B、C、D 和 sarray 可以存储在任何位置。 A、B、C 和 D 可能是从实习生池中获得的,对对象的引用可能是动态的。

        但我相信 JIT 可以足够聪明地减少一半的开销。

        PS。过早的优化是万恶之源。

        【讨论】:

          猜你喜欢
          • 2014-12-29
          • 2020-02-08
          • 2016-02-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-09
          相关资源
          最近更新 更多