c# - MSIL : Why does Array initializer use dup

标签 c# cil

我最近在学习 MSIL,对数组有些困惑: 以下 2 种方法:

private static void FormatTest3()
{
    string s = string.Format("{0}{1}{2}", 1, 2,3);
}

private static void FormatTest4()
{
    string s = string.Format("{0}{1}{2}{3}", 1, 2,3,4);
    /*
    equal to
    object[] obj = new object[4];
    obj[0] = 1;
    obj[1] = 2;
    obj[2] = 3;
    obj[3] = 4;
    string text = string.Format("{0}{1}{2}{3}", obj);
    */
}

这是 IL:

.class private auto ansi '<Module>'
{
} // end of class <Module>

.class private auto ansi beforefieldinit Program
    extends [System.Private.CoreLib]System.Object
{
    // Methods
    .method private hidebysig static 
        void FormatTest3 () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 31 (0x1f)
        .maxstack 4
        .locals init (
            [0] string
        )

        IL_0000: nop
        IL_0001: ldstr "{0}{1}{2}"
        IL_0006: ldc.i4.1
        IL_0007: box [System.Private.CoreLib]System.Int32
        IL_000c: ldc.i4.2
        IL_000d: box [System.Private.CoreLib]System.Int32
        IL_0012: ldc.i4.3
        IL_0013: box [System.Private.CoreLib]System.Int32
        IL_0018: call string [System.Private.CoreLib]System.String::Format(string, object, object, object)
        IL_001d: stloc.0
        IL_001e: ret
    } // end of method Program::FormatTest3

    .method private hidebysig static 
        void FormatTest4 () cil managed 
    {
        // Method begins at RVA 0x207c
        // Code size 55 (0x37)
        .maxstack 5
        .locals init (
            [0] string
        )

        IL_0000: nop
        IL_0001: ldstr "{0}{1}{2}{3}"
        IL_0006: ldc.i4.4
        IL_0007: newarr [System.Private.CoreLib]System.Object
        IL_000c: dup
        IL_000d: ldc.i4.0
        IL_000e: ldc.i4.1
        IL_000f: box [System.Private.CoreLib]System.Int32
        IL_0014: stelem.ref
        IL_0015: dup
        IL_0016: ldc.i4.1
        IL_0017: ldc.i4.2
        IL_0018: box [System.Private.CoreLib]System.Int32
        IL_001d: stelem.ref
        IL_001e: dup
        IL_001f: ldc.i4.2
        IL_0020: ldc.i4.3
        IL_0021: box [System.Private.CoreLib]System.Int32
        IL_0026: stelem.ref
        IL_0027: dup
        IL_0028: ldc.i4.3
        IL_0029: ldc.i4.4
        IL_002a: box [System.Private.CoreLib]System.Int32
        IL_002f: stelem.ref
        IL_0030: call string [System.Private.CoreLib]System.String::Format(string, object[])
        IL_0035: stloc.0
        IL_0036: ret
    } // end of method Program::FormatTest4

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20bf
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
        IL_0006: nop
        IL_0007: ret
    } // end of method Program::.ctor

} // end of class Program

我的问题是:

  1. 为什么带有 4 个或更多参数的 string.Format() 使用数组?
  2. 为什么 FormatTest4() 的 MSIL 使用 dup(我知道 dup 做什么)?

最佳答案

  1. 这是针对最常见情况的性能优化。通过对常见数量的参数进行单独重载,他们不必创建 params 数组参数,从而节省了分配(尽管可能仍需要装箱,但这比数组更便宜)。理论上,不需要存在 0、1、2 和 3 参数的重载,因为采用 params object[] 的方法也可以处理所有这些参数。只是更贵。

  2. dup 复制堆栈中的当前项目。 stelem.ref 从堆栈中取出三个项目,即数组、索引和该数组索引的值,并将该值存储在数组的索引处。这意味着数组引用之后不再在堆栈上。因此 dup。我们希望将该数组引用保留在堆栈顶部,因为我们需要将它传递给被调用的方法,因此我们创建一个数组,复制它,推送索引和第一项,使用 stelem.ref 将项目存储在数组中,并且仍然具有该数组引用,否则该引用将消失。

    有其他方法可以做到这一点。如果您使用从反编译的 C# 中复制的代码,您最终会得到不同的 IL,其中数组引用每次都取自局部变量:

    IL_0036: ldc.i4.4
    IL_0037: newarr [System.Private.CoreLib]System.Object
    IL_003c: stloc.1
    IL_003d: ldloc.1
    IL_003e: ldc.i4.0
    IL_003f: ldc.i4.1
    IL_0040: box [System.Private.CoreLib]System.Int32
    IL_0045: stelem.ref
    IL_0046: ldloc.1
    IL_0047: ldc.i4.1
    IL_0048: ldc.i4.2
    IL_0049: box [System.Private.CoreLib]System.Int32
    IL_004e: stelem.ref
    

    我认为这比 dup 效率低,但也许 JIT 并不真正关心这两种方式。不过,真正的反编译 C# 代码实际上应该是这样的:

    string text = string.Format("{0}{1}{2}{3}", new object[] { 1, 2, 3, 4 });
    

    这导致与

    相同的IL
    string text = string.Format("{0}{1}{2}{3}", 1, 2, 3, 4);
    

关于c# - MSIL : Why does Array initializer use dup,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56391609/

相关文章:

c# - 为什么在 C# 中,从函数参数引用变量比从私有(private)属性引用变量更快?

C# 编译器 + 带装箱的通用代码 + 约束

c# - 使用反射查找枚举器的方法

c# - 为什么 IL.Emit 方法要添加额外的 nop 指令?

c# - 在一个字符串方法中修剪和填充

.net - 在 IL 代码中,为什么在给定情况下没有 nop 操作码?为什么在给定情况下有 br.s 操作码?

c# 使用 lambda 查找范围内的值

c# -\tssr >"&\8=f23' 作为字符串 C#

c# - 需要帮助确定要编写的单元测试

c# - 从 XslCompiledTransform.Transform 输出中排除 XML 指令