Компилятор MS C # и неоптимизированный код - PullRequest
15 голосов
/ 07 сентября 2010

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

Официальный компилятор 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 ]

21 голосов
/ 07 сентября 2010

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

Впрочем, я попробую.Я думаю, что вопрос на самом деле что-то вроде: «какое дизайнерское решение заставляет компилятор выдавать объявление, хранить и загружать локальную # 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 его не использует »

Общее правило здесь: «делать локальные переменные из всех временных значений в неоптимизированной сборке».И вот, пожалуйста.чтобы оценить выражение «если», нам нужно оценить условие и преобразовать его в bool.(Конечно, условие не обязательно должно быть типа bool; это может быть тип, неявно преобразуемый в bool, или тип, который реализует пару оператор true / оператор false.) Генератору неоптимизированного кода было сказано "настойчиво переворачивать все временные значения".в местных жителей ", и вот что вы получаете.

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

2 голосов
/ 07 сентября 2010

На самом деле я не вижу проблемы, весь оптимизированный код был оптимизирован для одной локальной ссылки (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
{
  ...
}
1 голос
/ 07 сентября 2010

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

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

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

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

...