#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?
Комментариев нет:
Отправить комментарий