Достаточно ли умен компилятор C # для оптимизации этого кода? - PullRequest
26 голосов
/ 29 января 2010

Пожалуйста, игнорируйте читабельность кода в этом вопросе.

С точки зрения производительности, следующий код должен быть написан так:

int maxResults = criteria.MaxResults;

if (maxResults > 0)
{
    while (accounts.Count > maxResults)
        accounts.RemoveAt(maxResults);
}

или как это:

if (criteria.MaxResults > 0)
{
    while (accounts.Count > criteria.MaxResults)
        accounts.RemoveAt(criteria.MaxResults);
}

Редактировать: criteria - это class, а MaxResults - это простое целочисленное свойство (т.е. public int MaxResults { get { return _maxResults; } }.

Обрабатывает ли компилятор C # MaxResults как черный ящик и каждый раз оценивает его? Или он достаточно умен, чтобы выяснить, что у меня 3 вызова одного и того же свойства без изменения этого свойства между вызовами? Что если MaxResults было полем?

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

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

Ответы [ 6 ]

59 голосов
/ 29 января 2010

Во-первых, единственный способ ответить на вопросы о производительности - это попробовать оба способа и проверить результаты в реальных условиях.

Тем не менее, другие ответы, в которых говорится, что «компилятор» не выполняет эту оптимизацию, поскольку свойство может иметь побочные эффекты, являются как правильными, так и неправильными. Проблема с вопросом (помимо фундаментальной проблемы, на которую просто невозможно ответить, не пытаясь на самом деле это сделать и не измерив результат), заключается в том, что «компилятор» - это на самом деле два компилятора: компилятор C #, который компилируется в MSIL, и компилятор JIT , который компилирует IL в машинный код.

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

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

Является ли на самом деле джиттер встроенным извлечением свойства и затем регистрирует значение, я понятия не имею. Я практически ничего не знаю о джиттере. Но это разрешено делать, если сочтет нужным. Если вам интересно узнать, так ли это или нет, вы можете (1) спросить кого-то из команды, написавшей джиттер, или (2) проверить код в коде отладчика.

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

  • время исполнения

  • размер исполняемого кода - это существенно влияет на время выполнения, поскольку загрузка большого кода занимает больше времени, увеличивает размер рабочего набора, оказывает давление на кэш-память процессора, ОЗУ и файл подкачки. Небольшой медленный код часто в долгосрочной перспективе быстрее , чем большой быстрый код в важных показателях, таких как время запуска и локальность кэша.

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

  • и так далее. Это очень сложно очень быстро.

Короче говоря, вы не можете знать, является ли запись кода для кэширования результата, а не перерасчет, на самом деле (1) быстрее или (2) более эффективной. Повышение производительности не всегда означает ускорение выполнения конкретной подпрограммы. Повышение производительности - это выяснение того, какие ресурсы важны для пользователя - время выполнения, память, рабочий набор, время запуска и т. Д. - - и оптимизировать для этих вещей. Вы не можете сделать это без (1) разговора с вашими клиентами, чтобы выяснить, о чем они заботятся, и (2) фактического измерения, чтобы увидеть, оказывают ли ваши изменения ощутимый эффект в желаемом направлении.

7 голосов
/ 29 января 2010

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

private int _maxResults;
public int MaxReuslts {
  get { return _maxResults++; }
  set { _maxResults = value; }
}

Посмотрите, как изменится поведение, если оно будет встроено в ваш код?

Если нет логики ... любой метод, который вы написали, подойдет, это очень маленькая разница, и все о том, насколько он читабелен ДЛЯ ВАС (или вашей команды) ... вы один глядя на это.

6 голосов
/ 29 января 2010

Ваши два примера кода гарантированно будут иметь один и тот же результат только в однопоточных средах, которых нет в .Net, и если MaxResults - это поле (а не свойство).Компилятор не может предположить, если вы не используете функции синхронизации, что criteria.MaxResults не изменится в течение вашего цикла.Если это свойство, оно не может предполагать, что использование свойства не имеет побочных эффектов.

Эрик Липперт совершенно правильно указывает, что это во многом зависит от того, что вы подразумеваете под «компилятором».Компилятор C # -> IL?Или IL -> компилятор машинного кода (JIT)?И он прав, отметив, что JIT вполне может оптимизировать метод получения свойств, поскольку он содержит всю информацию (в то время как компилятор C # -> IL этого не делает).Это не изменит ситуацию с несколькими потоками, но, тем не менее, это хороший момент.

4 голосов
/ 29 января 2010

Он будет вызываться и оцениваться каждый раз. Компилятор не может определить, является ли метод (или метод получения) детерминированным и чистым (без побочных эффектов).

Обратите внимание, что фактическая оценка свойства может быть встроена компилятором JIT, что делает его эффективным так же быстро, как простое поле.

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

2 голосов
/ 29 января 2010

почему бы не проверить это?

просто установите 2 консольных приложения, чтобы они выглядели 10 миллионов раз и сравнивали результаты ... не забудьте запустить их как правильно выпущенные приложения, которые были установлены правильно, иначе вы не сможете гарантировать, что вы не просто запускаете msil.

На самом деле вы, вероятно, получите около 5 ответов, в которых говорится: «Вам не стоит беспокоиться об оптимизации». они явно не пишут процедуры, которые должны быть максимально быстрыми, чтобы их можно было прочитать (например, игры).

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

Действительно, ONLY способ ответить на любой из этих вопросов - выяснить, является ли это фрагментом кода, который выиграет от оптимизации. Тогда вам нужно знать, какие вещи увеличивают время выполнения. На самом деле мы, простые смертные, не можем сделать это априори и поэтому должны просто попробовать 2-3 разные версии кода и затем протестировать его.

0 голосов
/ 29 января 2010

Если criteria является типом класса, я сомневаюсь, что он будет оптимизирован, потому что другой поток всегда может изменить это значение в то же время. Для struct s я не уверен, но мое внутреннее чувство состоит в том, что он не будет оптимизирован, но я думаю, что в любом случае это не сильно повлияет на производительность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...