Страницы

Поиск по вопросам

воскресенье, 22 декабря 2019 г.

Разный IL для инициализации свойств со значениями

#c_sharp


Рассматриваю в Linqpad'е IL-код простенького итератора:

void Main()
{
    var a = new A();
    a.Skip(2).Take(5).Dump();
}

public class A : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        return new B();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class B : IEnumerator
{
    public int Current
    {
        get
        {
            return 123;
        }
    }

    object IEnumerator.Current { get { return this.Current; } }

    public void Dispose() {}

    public bool MoveNext() { return true; }

    public void Reset() {}
}


Любопытно сравнение кода, если воспользоваться новой возможностью c# 6 Инициализация
свойств со значениями и заменить:

    public int Current
    {
        get
        {
            return 123;
        }
    }


на:

   public int Current => 123;


Вот что будет в IL для первого случая:

B.get_Current:
IL_0000:  nop         
IL_0001:  ldc.i4.s    7B 
IL_0003:  stloc.0     
IL_0004:  br.s        IL_0006
IL_0006:  ldloc.0     
IL_0007:  ret 


и второго:

B.get_Current:
IL_0000:  ldc.i4.s    7B 
IL_0002:  ret 


Обычно новые фичи языка утяжеляют конструкции, но тут обратный случай: код чище и
быстрее.

Я не понимаю, что помешало комплятору в первом случае выкинуть явно лишние команды.
В обоих случаях Current – свойство get-only, простая константа.

При этом если рассматривать классическую замену, то происходят абсолютно аналогичные
вещи.

Для

void Main()
{
    var abc = new Abc {};
}

public class Abc
{
    private int _id = 123; // backing field
    public int Id
    {
        get { return _id; }
        set { _id = value; }
    }
}


будет сгенерирован такой код:

IL_0000:  nop         
IL_0001:  newobj      UserQuery+Abc..ctor
IL_0006:  stloc.0     // abc
IL_0007:  ret         

Abc.get_Id:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldfld       UserQuery+Abc._id
IL_0007:  stloc.0     
IL_0008:  br.s        IL_000A
IL_000A:  ldloc.0     
IL_000B:  ret         

Abc.set_Id:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.1     
IL_0003:  stfld       UserQuery+Abc._id
IL_0008:  ret         

Abc..ctor:
IL_0000:  ldarg.0     
IL_0001:  ldc.i4.s    7B 
IL_0003:  stfld       UserQuery+Abc._id
IL_0008:  ldarg.0     
IL_0009:  call        System.Object..ctor
IL_000E:  nop         
IL_000F:  ret  


А для

    public int Id { get; set; } = 123;


оптимизированный:

IL_0000:  nop         
IL_0001:  newobj      UserQuery+Abc..ctor
IL_0006:  stloc.0     // abc
IL_0007:  ret         

Abc.get_Id:
IL_0000:  ldarg.0     
IL_0001:  ldfld       UserQuery+Abc.k__BackingField
IL_0006:  ret         

Abc.set_Id:
IL_0000:  ldarg.0     
IL_0001:  ldarg.1     
IL_0002:  stfld       UserQuery+Abc.k__BackingField
IL_0007:  ret         

Abc..ctor:
IL_0000:  ldarg.0     
IL_0001:  ldc.i4.s    7B 
IL_0003:  stfld       UserQuery+Abc.k__BackingField
IL_0008:  ldarg.0     
IL_0009:  call        System.Object..ctor
IL_000E:  nop         
IL_000F:  ret


Вообще, что делает соптимизированный участок кода, какую роль он выполняет в старой
версии кода?

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


Ответы

Ответ 1



Вообще вопрос сам по себе достаточно бессмысленный. Вы сравниваете результат компиляции в IL, явно указываете что собираете с отключенной отптимизацией я в подобных случаях проверяю, что оптимизации выключены, не забыл и сейчас. Отключено, отключено - можете сами проверить. Вон же отладочные nop'ы для брейков в коде видны. но и при этом пытаетесь сравнить "оптимизированность" кода. Т.е. вы явно запретили компилятору оптимизировать, и делаете из последствий вашего запрета вывод, что компилятор иногда выдает "неоптимизированный код" :) Если хотите получить оптимизированный код - включите оптимизацию. Если хотите получить код с отладочными вставками - выключите оптимизацию. Вот этот артефакт: IL_0007: stloc.0 IL_0008: br.s IL_000A IL_000A: ldloc.0 позволяет вам поставить брекпойнт на закрывающую скобку в get { return _id; } В случае нескольких return в методе - вы, скорее всего, получите пачку безусловных переходов на одну и ту же метку: get { if (some) { return _id; } else { return 42; } } // брекпойнт на этой строке срабатывает всегда! IL: IL_0000: nop IL_0001: ldarg.0 IL_0002: ldfld bool ConsoleApp1.Abc::some IL_0007: stloc.0 IL_0008: ldloc.0 IL_0009: brfalse.s IL_0015 IL_000b: nop IL_000c: ldarg.0 IL_000d: ldfld int32 ConsoleApp1.Abc::_id IL_0012: stloc.1 IL_0013: br.s IL_001b IL_0015: nop IL_0016: ldc.i4.s 42 IL_0018: stloc.1 IL_0019: br.s IL_001b IL_001b: ldloc.1 IL_001c: ret При отключенной отптимизации компилятор старается сделать код удобным для отладки, и, кроме nop-ов, вставляет вот такие вот костыли с явным сохранением возвращаемого значения в локальную переменную. Включите оптимизацию - и отладочные хаки из IL исчезнут. enSO: Why is the 'br.s' IL opcode used in this case? Why does this very simple C# method produce such illogical CIL code?

Комментариев нет:

Отправить комментарий