Рассматриваю в Linqpad'е IL-код простенького итератора:
void Main()
{
var a = new A();
a.Skip(2).Take(5).Dump();
}
public class A : IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class B : IEnumerator
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.
Abc.set_Id:
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld UserQuery+Abc.
Abc..ctor:
IL_0000: ldarg.0
IL_0001: ldc.i4.s 7B
IL_0003: stfld UserQuery+Abc.
Вообще, что делает соптимизированный участок кода, какую роль он выполняет в старой версии кода?
Выигрыш в коде получается копеечный, но выходит что в целях микрооптимизаций (если вдруг дойдёт до них) предпочтительнее использовать новую конструкцию.
Ответ
Вообще вопрос сам по себе достаточно бессмысленный.
Вы сравниваете результат компиляции в 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?
Комментариев нет:
Отправить комментарий