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

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

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

например, простой оператор 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:

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

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

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

3 ответов


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

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

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

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/оператор false.) Генератор неоптимизированного кода было сказано "агрессивно превратить все временные ценности в местных жителей", и вот что вы получите.

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


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

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

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

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

Я вижу, что происходит. Из-за того, что логические значения представлены как 0 и !0, отладочная версия выполняет второе сравнение. С ДРУГОЙ СТОРОНЫ, оптимизатор, вероятно, может что-то доказать о безопасности кода.

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

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
{
  ...
}

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

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

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

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