Понимание оптимизации компилятора - PullRequest
0 голосов
/ 18 января 2019

Я пытаюсь понять, что делает компилятор с очень простым фрагментом кода:

if (group.ImageHeight > 1 && group.ImageWidth > 1)
{ //No code exists between the braces
}

После компиляции в Debug конфигурации, затем декомпиляции я вижу это:

if (group.ImageHeight <= 1 || group.ImageWidth <= 1);

Декомпиляция Release конфигурации приводит к

if (group.ImageHeight > 1)
{
  int imageWidth = group.ImageWidth;
}

Более полный (оригинальный) код:

public class Group 
{
  public int ImageHeight { get; set; }
  public int ImageWidth { get; set; }
}

//The following belongs to a different project than `Group`
static void Main(string[] args)
{
  Group group = new Group();
  MyMethod(group);
}
static void MyMethod(Group group)
{
    if (group.ImageHeight > 1 && group.ImageWidth > 1)
    { 
    }
}

Вот мои догадки и наблюдения:

  • Когда я впервые начал это, я ожидал, что компилятор полностью отбросит весь оператор. Я думаю, что это не потому, что оценка свойств может иметь побочные эффекты.
  • Я считаю, что важно, чтобы тип group принадлежал другому проекту в моем решении. Я говорю это потому, что компилятор, вероятно, не может «знать», какими могут быть побочные эффекты при оценке свойств в будущем. Например, я мог бы после компиляции заменить DLL, содержащую определение для group.
  • В конфигурации Release возможные побочные эффекты выглядят так же, как и мой код: ImageHeight оценивается, и при выполнении условия > 1 оценивается ImageWidth (хотя посредством назначения, а не сравнения)

Теперь, для моих конкретных вопросов:

  • Почему в конфигурации Release используется назначение (int imageWidth = group.ImageWidth), а не мое первоначальное сравнение? Это быстрее выполнить задание?
  • Почему конфигурация Debug полностью меняет возможность побочных эффектов? В этой конфигурации всегда будут оцениваться значения ImageHeight и ImageWidth.

1 Ответ

0 голосов
/ 18 января 2019

По первому конкретному вопросу. Когда вы смотрите на IL на sharplab.io Простое назначение - 1 короткая инструкция сравнения. Чьи «then» и «else» будут указывать на одну и ту же инструкцию (в данном случае IL_0012), так что сравнивать не нужно для вызова функции, и достаточно двух всплывающих окон. Странно загружать только константу Int32 1, которая будет немедленно отброшена.

if (group.ImageHeight> 1)

IL_0000: ldarg.0
IL_0001: callvirt instance int32 Group::get_ImageHeight()
IL_0006: ldc.i4.1
IL_0007: ble.s IL_0012

int imageWidth = group.ImageWidth;

IL_0009: ldarg.0
IL_000a: callvirt instance int32 Group::get_ImageWidth()
IL_000f: ldc.i4.1
IL_0010: pop
IL_0011: pop

IL_0012: ret

По второму конкретному вопросу. Если вы посмотрите на IL на той же странице с режимом отладки, то увидите, что код идентичен только некоторым дополнительным инструкциям по отладке и самому сравнению, так что вы можете посмотреть результат в отладчике.

IL_0000: nop
IL_0001: ldarg.0
IL_0002: callvirt instance int32 Group::get_ImageHeight()
IL_0007: ldc.i4.1
IL_0008: ble.s IL_0015

IL_000a: ldarg.0
IL_000b: callvirt instance int32 Group::get_ImageWidth()
IL_0010: ldc.i4.1
IL_0011: cgt
IL_0013: br.s IL_0016

IL_0015: ldc.i4.0

IL_0016: stloc.0
        // sequence point: hidden
IL_0017: ldloc.0
IL_0018: brfalse.s IL_001c

IL_001a: nop
IL_001b: nop

IL_001c: ret
...