Странный код при использовании инициализатора массива
От: Cynic Россия  
Дата: 20.08.15 12:05
Оценка: 5 (1)
Как известно в C# можно инициализировать массивы в месте их объявления указывая значения в фигурных скобках. Я так всегда и делал, но тут решил посмотреть во что-же это превращается в IL. Оказалось что такой вот код:
static void Main()
{
    var arr = new[] { 1, 2 };

    Console.WriteLine(arr);     // если не использовать переменную arr где нибудь, то её оптимизирует компилятор
}

превращается вот в это:
.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] int32[] numArray,
        [1] int32[] numArray2)      // это явно лишнее
    L_0000: ldc.i4.2 
    L_0001: newarr int32
    L_0006: stloc.1 
    L_0007: ldloc.1 
    L_0008: ldc.i4.0 
    L_0009: ldc.i4.1 
    L_000a: stelem.i4 
    L_000b: ldloc.1 
    L_000c: ldc.i4.1 
    L_000d: ldc.i4.2 
    L_000e: stelem.i4 
    L_000f: ldloc.1 
    L_0010: stloc.0 
    L_0011: ldloc.0 
    L_0012: call void [mscorlib]System.Console::WriteLine(object)
    L_0017: ret 
}

Вопрос зачем компилятор создает две ссылки numArray когда хватило бы и одной? Причём он генерирует такой код только если выполняешь инициализацию массива используя синтаксис фигурных скобок, во всех других случаях генерируется оптимальный код.

P/S — Оптимизация если что включена!
:)
Re: Странный код при использовании инициализатора массива
От: Sharov Россия  
Дата: 20.08.15 12:23
Оценка: +1
Здравствуйте, Cynic, Вы писали:

C>Вопрос зачем компилятор создает две ссылки numArray когда хватило бы и одной? Причём он генерирует такой код только если выполняешь инициализацию массива используя синтаксис фигурных скобок, во всех других случаях генерируется оптимальный код.


Мож какая временная переменная Во всяком случае похоже на это.
Кодом людям нужно помогать!
Re: Странный код при использовании инициализатора массива
От: MxMsk Португалия  
Дата: 20.08.15 12:36
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Вопрос зачем компилятор создает две ссылки numArray когда хватило бы и одной? Причём он генерирует такой код только если выполняешь инициализацию массива используя синтаксис фигурных скобок, во всех других случаях генерируется оптимальный код.

Критерий оптимальности сомнительный. ЕМНИП, там идея в том, что массив надо присвоить полностью инициализированным. Поэтому операции заполнения массива делаются с доп. переменной, значение которой потом перемещается в фактическую.
Re[2]: Странный код при использовании инициализатора массива
От: Sinclair Россия https://github.com/evilguest/
Дата: 20.08.15 12:59
Оценка: 4 (2) +2
Здравствуйте, MxMsk, Вы писали:

C>>Вопрос зачем компилятор создает две ссылки numArray когда хватило бы и одной? Причём он генерирует такой код только если выполняешь инициализацию массива используя синтаксис фигурных скобок, во всех других случаях генерируется оптимальный код.

MM>Критерий оптимальности сомнительный. ЕМНИП, там идея в том, что массив надо присвоить полностью инициализированным. Поэтому операции заполнения массива делаются с доп. переменной, значение которой потом перемещается в фактическую.
Для локальной переменной это неважно, т.к. кода, который бы мог пронаблюдать её недоинциализированной, не существует.
Скорее всего, compiler team просто пошла по пути наименьшего сопротивления, всегда порождая временную переменную для инициализации.
Это важно для тех случаев, когда финальный результат присваивается не в локальную переменную, а в object member, который может быть неожиданно прочитан из другого потока.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Странный код при использовании инициализатора массива
От: Sinix  
Дата: 20.08.15 13:14
Оценка: 1 (1)
Здравствуйте, Cynic, Вы писали:

C>Вопрос зачем компилятор создает две ссылки numArray когда хватило бы и одной?

Отладочная сборка. В релизной вообще .locals не будет.
{
    // Method begins at RVA 0x2050
    // Code size 20 (0x14)
    .maxstack 8
    .entrypoint

    IL_0000: ldc.i4.2
    IL_0001: newarr [mscorlib]System.Int32
    IL_0006: dup
    IL_0007: ldc.i4.0
    IL_0008: ldc.i4.1
    IL_0009: stelem.i4
    IL_000a: dup
    IL_000b: ldc.i4.1
    IL_000c: ldc.i4.2
    IL_000d: stelem.i4
    IL_000e: call void [mscorlib]System.Console::WriteLine(object)
    IL_0013: ret
} // end of method Program::Main


Немного магии: если добавите в массив ещё один элемент — случится страшное.
Re[2]: Странный код при использовании инициализатора массива
От: Cynic Россия  
Дата: 20.08.15 14:14
Оценка: 24 (2)
Здравствуйте, Sinix, Вы писали:

S>Отладочная сборка. В релизной вообще .locals не будет.


А у меня даже в релизной версии все равно две переменные:
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       24 (0x18)
  .maxstack  3
  .locals init ([0] int32[] arr,
           [1] int32[] CS$0$0000)        // всё равно два штука!
  IL_0000:  ldc.i4.2
  IL_0001:  newarr     [mscorlib]System.Int32
  IL_0006:  stloc.1
  IL_0007:  ldloc.1
  IL_0008:  ldc.i4.0
  IL_0009:  ldc.i4.1
  IL_000a:  stelem.i4
  IL_000b:  ldloc.1
  IL_000c:  ldc.i4.1
  IL_000d:  ldc.i4.2
  IL_000e:  stelem.i4
  IL_000f:  ldloc.1
  IL_0010:  stloc.0
  IL_0011:  ldloc.0
  IL_0012:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0017:  ret
} // end of method Program::Main
:)
Re[3]: Странный код при использовании инициализатора массива
От: MxMsk Португалия  
Дата: 20.08.15 14:17
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Для локальной переменной это неважно, т.к. кода, который бы мог пронаблюдать её недоинциализированной, не существует.

S>Скорее всего, compiler team просто пошла по пути наименьшего сопротивления, всегда порождая временную переменную для инициализации.
S>Это важно для тех случаев, когда финальный результат присваивается не в локальную переменную, а в object member, который может быть неожиданно прочитан из другого потока.
Понятное дело. А я только про идею написал.
Re[3]: Странный код при использовании инициализатора массива
От: Sinix  
Дата: 20.08.15 14:33
Оценка:
Здравствуйте, Cynic, Вы писали:

C>А у меня даже в релизной версии все равно две переменные:


Ага, в рослине поменяли. На старых версиях действительно две переменные. Поддерживаю версию Sinclair-а.

Скорее всего, compiler team просто пошла по пути наименьшего сопротивления, всегда порождая временную переменную для инициализации.

Re: Странный код при использовании инициализатора массива
От: nikov США http://www.linkedin.com/in/nikov
Дата: 20.08.15 17:16
Оценка: 107 (8) +2
Здравствуйте, Cynic, Вы писали:

C>Вопрос зачем компилятор создает две ссылки numArray когда хватило бы и одной? Причём он генерирует такой код только если выполняешь инициализацию массива используя синтаксис фигурных скобок, во всех других случаях генерируется оптимальный код.


Потому что инициализатор работает так: (1) сначала создаётся массив, (2) потом инициализируются его элементы, (3) потом ссылка на массив присваивается локальной переменной. До шага (3) ты можешь использовать эту локальную переменную для других целей, если тебе вздумается, и это никак не должно повлиять на результат инициализации. Как же можно её использовать, если она ещё не проинициализирована? Очень проcто: в инициализаторах массива могут быть произвольные выражения, которые могут присваивать что-то этой переменной, передавать её как ref или out параметр в какие-то методы и т.д. Например:

using System;

class Program
{
    static void Main()
    {
        int[] x = { 1, Foo(out x), Bar(x) };

        Console.WriteLine();
        foreach (var e in x)
        {
            Console.Write(e);
        }

        Console.WriteLine();

    }

    static int Foo(out int[] x)
    {
        x = new[] { 4, 5 };
        return 2;
    }

    static int Bar(int[] x)
    {
        foreach (var e in x)
        {
            Console.Write(e);
        }

        return 3;
    }
}


Результат:

45
123


Пара замечаний:
* этот трюк не пройдёт, если тип переменной объявлен как var.
* компилятор мог бы заметить что мы не пытаемся делать ничего подобного и всё-таки оптимизировать эту переменную.

Компилятор это и делает, если в инициализаторе достаточно большое количество элементов и все они константы. Почему же он не делает этого в данном случае? Потому что компилятор не может делать всех возможных оптимизаций. Все они требуют ресурсов (дизайн, разработка, тестирование), и в некоторых случаях потенциальный выигрыш не оправдывает этих вложений, и ресурсы лучше потратить на что-то другое (новые фичи, баг-фиксинг). Кроме того, с некоторыми оптимизациями лучше справляется JIT.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.