Является ли компилятор языка С# выполнять какие-либо фактические оптимизации самостоятельно?

Основываясь на случайных комментариях в Интернете, я всегда считал, что компилятор С# делает простые оптимизации для IL (удаление всегда-истинных if-statement, простых вложений и т.д.), а затем JIT выполняет реальные сложные вычисления.

Как только один пример, в документации для флага /optimize компилятора, он говорит

Параметр/optimize включает или отключает оптимизацию, выполняемую компилятором , чтобы сделать ваш выходной файл меньше, быстрее и эффективнее.

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


Однако, играя с Try Roslyn, это выглядит неправдой. Похоже, что компилятор С# практически не оптимизирует.

Примеры

Input:

bool y = true;
if (y)
    Console.WriteLine("yo");

Декомпилированный вывод:

if (true)
{
    Console.WriteLine("yo");
}

Input:

static void DoNothing() { }

static void Main(string[] args)
{
    DoNothing();
    Console.WriteLine("Hello world!");
}

Декомпилированный вывод:

private static void DoNothing()
{
}
private static void Main(string[] args)
{
    NormalProgram.DoNothing();
    Console.WriteLine("Hello world!");
}

Input:

try
{
    throw new Exception();
}
catch (Exception)
{
    Console.WriteLine("Hello world!");
}

Декомпилированный вывод:

try
{
    throw new Exception();
}
catch (Exception)
{
    Console.WriteLine("Hello world!");
}

Как вы можете видеть, компилятор языка С# не выполняет никаких оптимизаций.

Это правда? Если да, то почему в документации утверждается, что /optimize сделает ваш исполняемый файл меньше?

Ответ 1

Единственная оптимизация, о которой я могу думать, что компилятор С#, который вы действительно можете увидеть с помощью декомпилятора, - это добавить пустой статический конструктор.

Они, как правило, довольно неинтересны, просто более компактные IL. Вы можете видеть их только при взгляде на ИЛ, достойный декомпилятор не покажет его. Неоптимизированный код имеет типичные артефакты генератора кода рекурсивно-достойного компилятора, резервные хранилища, за которыми немедленно следует загрузка одной и той же переменной, ветки на следующий адрес. Оптимизатор знает, как их устранить. Стандартным примером являются NOP, которые испускаются для облегчения отладки, они позволяют установить точку останова на фигурной скобке. Удалено оптимизатором.

Ничего, что обеспечило бы легко наблюдаемое совершенство перформанса, хотя вам может быть повезло, что более компактный IL просто дает оптимизатору джиттера достаточное время для устранения критического хранилища памяти. Это происходит нечасто.

Ответ 2

/optimize флаг говорит компилятору: "Эй, этот код пытается оптимизировать, насколько это возможно", но это не значит, что это оптимизация флага Святого Грааля.
Флаг /optimize делает много вещей, таких как удаление неиспользуемых переменных, замена объявленных переменных, но не используемых (например, ваш пример), номер суммы в коде (например, int a = 2+2; становится int a = 4; и многие другие вещи, которые вы видите в курсе здесь