MSIL: почему инициализатор Array использует dup - PullRequest
1 голос
/ 31 мая 2019

Я недавно изучаю 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. Почему string.Format () с 4 или более параметрами использует массивы?
  2. Почему MSIL FormatTest4 () использует dup (я знаю, что делает dup)?

1 Ответ

6 голосов
/ 31 мая 2019
  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);
    
...