Подтвердить что ты не робот

Является ли компилятор С# достаточно умным, чтобы оптимизировать этот код?

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

  • и т.д. Это становится очень сложным.

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

Ответ 2

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

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

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

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

Ответ 3

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

Эрик Липперт совершенно правильно указывает, что это сильно зависит от того, что вы подразумеваете под "компилятором". Компилятор С# → IL? Или компилятор IL → машинного кода (JIT)? И он имеет право указать, что JIT вполне может оптимизировать свойство getter, так как он имеет всю информацию (тогда как компилятор С# → IL не обязательно). Это не изменит ситуацию с несколькими потоками, но это тем не менее.

Ответ 4

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

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

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

Ответ 5

почему бы не протестировать его?

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

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

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

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

Ответ 6

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