MS С# компилятор и не оптимизированный код

Примечание. Я заметил некоторые ошибки в моем опубликованном примере - редактирование, чтобы исправить его

Официальный компилятор С# делает некоторые интересные вещи, если вы не включаете оптимизацию.

Например, простой оператор if:

int x;
// ... //
if (x == 10)
   // do something

в случае оптимизации оптимизируется:

ldloc.0
ldc.i4.s 10
ceq
bne.un.s do_not_do_something
// do something
do_not_do_something:

но если мы отключим оптимизацию, это будет примерно так:

ldloc.0
ldc.i4.s 10
ceq
ldc.i4.0
ceq
stloc.1
ldloc.1
brtrue.s do_not_do_something
// do something
do_not_do_something:

Я не могу полностью обдумать это. Почему весь этот дополнительный код, который, по-видимому, отсутствует в источнике? В С# это будет эквивалентно:

int x, y;
// ... //
y = x == 10;
if (y != 0)
   // do something

Кто-нибудь знает, почему он это делает?

Ответы

Ответ 1

Я не совсем понимаю суть вопроса. Похоже, вы спрашиваете: "Почему компилятор создает неоптимизированный код, когда переключатель оптимизации отключен?" который сам отвечает.

Однако, я возьму на него удар. Я думаю, что вопрос на самом деле что-то вроде "какое конструктивное решение заставляет компилятор выпустить объявление, хранить и загружать локальные # 1, которые можно оптимизировать?"

Ответ заключается в том, что неоптимизированный кодеген разработан, чтобы быть ясным, однозначным, легко отлаживать и поощрять джиттер генерировать код, который не агрессивно собирает мусор. Одним из способов достижения всех этих целей является создание локалей для большинства значений, которые идут в стек, даже временных значений. Давайте рассмотрим более сложный пример. Предположим, что у вас есть:

Foo(Bar(123), 456)

Мы могли бы сгенерировать это как:

push 123
call Bar - this pops the 123 and pushes the result of Bar
push 456
call Foo

Это хорошо, эффективно и мало, но это не соответствует нашим целям. Он ясен и недвусмыслен, но отлаживать его непросто, потому что сборщик мусора может стать агрессивным. Если Foo по какой-то причине фактически ничего не делает с его первым аргументом, GC может вернуть возвращаемое значение Bar до запуска Foo.

В неоптимизированной сборке мы будем генерировать нечто большее, чем

push 123
call Bar - this pops the 123 and pushes the result of Bar
store the top of the stack in a temporary location - this pops the stack, and we need it back, so
push the value in the temporary location back onto the stack
push 456
call Foo

Теперь у дрожания есть большой намек, в котором говорится: "Эй, дрожь, держи это живое в локальном какое-то время, даже если Foo его не использует"

Общее правило здесь - "сделать локальные переменные из всех временных значений в неоптимизированной сборке". И так вы идете; для оценки выражения "if" нам нужно оценить условие и преобразовать его в bool. (Конечно, условие не должно быть типа bool, оно может быть типа, неявно конвертируемого в bool, или типа, реализующего ложную пару оператора true/operator.) Неоптимизированному генератору кода было сказано "агрессивно поворачивать все временные значения на локальных жителей", и так, что вы получите.

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

Ответ 2

Я действительно не вижу проблемы, весь оптимизированный код был оптимизирован для одного локального локального локатора (stloc ldloc combo).

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

Изменить: теперь я вижу другой дополнительный ceq.

Обновление 2:

Я вижу, что происходит. Из-за булевых чисел, представленных как 0 и! 0, версия отладки выполняет второе сравнение. OTOH, оптимизатор может, вероятно, доказать что-то о безопасности кода.

Неоптимизированный код действительно будет выглядеть следующим образом:

int x, _local; // _local is really bool

_local = (x == 10) == 0;  // ceq is ==, not <, not sure why you see that
if (_local)  // as in C, iow _local != 0 implied
{
  ...
}

Ответ 3

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

Однако это, как правило, просто артефакт генерации кода, где общие процедуры написаны для обработки многих разных случаев для конкретного оператора, такого как if в вашем случае.

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

Другими причинами отсутствия менее оптимального кода при компиляции в режиме отладки является поддержка отладчика, например, команда NOP может быть вставлена ​​в код для облегчения точки останова при запуске в отладчике, но удалена для релизов.