Пустой if-блок не оптимизирован?

#c# #optimization #jit #cil

#c# #оптимизация #джит #cil

Вопрос:

Будет ли следующий пустой if-блок оптимизирован?

 public class C 
{
    private bool foo = false;
    
    public void M() 
    {
        if(foo) {}
    }
}
 

SharpLab (master 5 декабря 2020 года, выпуск) указывает, что компилятор не оптимизирует if-блок:

 .method public hidebysig instance void M () cil managed 
{
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldfld bool C::foo
    IL_0006: pop
    IL_0007: ret
}
 

Должен ли компилятор не видеть, что ldfld followed by pop не имеет никакого эффекта, и три инструкции ( ldarg.0 тоже) не нужно отправлять?

Я не могу думать о возможных побочных эффектах, которые могли бы возникнуть при отсутствии излучения ldfld (как это может быть при вызове метода внутри if-блока).


Кроме того, JIT ASM, который генерирует SharpLab, также, похоже, не оптимизирует пустой if-блок:

 C.M()
    L0000: movzx eax, byte ptr [ecx 4]
    L0004: ret
 

Правильно ли я понимаю, что JIT все равно оптимизирует пустой if-блок?

  • Если да, не могли бы вы, пожалуйста, объяснить, почему компилятор не может этого сделать?
  • Если нет, то почему это не так?

Заранее благодарю вас!

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

1. Вы создаете эту цель как release или debug? Я думаю, что при отладке определенных оптимизаций у меня не происходит.

Ответ №1:

Кроме того, JIT ASM, который генерирует SharpLab, также, похоже, не оптимизирует пустой if-блок:

 C.M()
    L0000: movzx eax, byte ptr [ecx 4]
    L0004: ret
 

Фактический if блок был оптимизирован, если бы он существовал, он выглядел бы сравнимо с этим:

 C.M()
    L0000: movzx eax, byte ptr [ecx 4]
    L0004: test eax, eax
    L0006: je short L0008
    L0008: ret
 

Но этого не произошло, JIT-компилятор, по-видимому, понял, что это будет бесполезно.

Все еще существует только загрузка логического поля.

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

1. Спасибо! Я плохо разбираюсь в сборке и был слишком сосредоточен не только на JIT ASM ret , поэтому я пришел к неправильному выводу… однако знаете ли вы, почему загрузка логического поля все еще существует? Как насчет вашей (теперь отредактированной) существующей точки загрузки, которая null не может быть передана методу?

2. @ThomasFlinkow фиктивная загрузка вставляется в вызывающий объект, если это необходимо (например, как это , где mov eax, [rdx] ничего, кроме запуска NRE, не происходит), так что, в конце концов, это не так. Как бы это ни было скучно, я должен объяснить это тем, что компилятор JIT немного тупой.

3. Оптимизация загрузки может привести к заметным изменениям в поведении. Если вы используете call метод вместо callvirt on и передаете null as this , исходный код все равно будет вызывать исключение NullReferenceException при загрузке, в то время как без него метод возвращался бы нормально. Может быть интересно посмотреть static , ведут ли себя поля одинаково.

4. @IllidanS4supportsMonica изначально это была гипотеза, но в вызывающий объект вставляется фиктивная загрузка, поэтому, если this это null так, метод даже не вызывается, и ему не нужно аварийно завершать работу

5. Я думаю, что компилятор настолько туп.