c# компилятор оптимизирует свойства Count?

List<int> list = ...

for(int i = 0; i < list.Count; ++i)
{
           ...
}

Делает компилятор, знают список. Количество нельзя назвать каждым повторением?

12
задан Vitaliy 20 July 2010 в 21:28
поделиться

7 ответов

Вы уверены в этом?

List<int> list = new List<int> { 0 };

for (int i = 0; i < list.Count; ++i)
{
    if (i < 100)
    {
        list.Add(i + 1);
    }
}

Если бы компилятор кэшировал свойство Count, указанное выше, содержимым list были бы 0 и 1. Если бы он этого не сделал, содержимым были бы целые числа от 0 до 100.

Теперь, возможно, этот пример покажется вам надуманным, но как насчет этого?

List<int> list = new List<int>();

int i = 0;
while (list.Count <= 100)
{
    list.Add(i++);
}

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

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

22
ответ дан 2 December 2019 в 03:48
поделиться

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

Свойство счетчика списка может изменяться в структуре цикла, поэтому это будет неправильная оптимизация.

9
ответ дан 2 December 2019 в 03:48
поделиться

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

Так что в примере, приведенном в посте (в котором может не быть никакого изменения Count), неразумно ли для JIT определять, что код в цикле не изменяет внутреннюю переменную, которую List использует для хранения своей длины? Если он обнаружит, что list.Count постоянна, разве он не уберет доступ к этой переменной из тела цикла?

Я не знаю, делает это JIT или нет. Но я не спешу отмахиваться от этой проблемы как от тривиального "никогда"

.
1
ответ дан 2 December 2019 в 03:48
поделиться

Если вы посмотрите на IL, созданный для примера Дэна Тао, то увидите в условии цикла такую строку:

callvirt instance int32 [mscorlib]System.Collections.Generic.List`1<int32>::get_Count()

Это неоспоримое доказательство того, что Count (т.е. get_Count()) вызывается на каждой итерации цикла.

3
ответ дан 2 December 2019 в 03:48
поделиться

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

Рассмотрим следующие случаи:

  • Сторонняя реализация свойства под названием «Count» может выполнять любой код, который она пожелает. например вернуть случайное число для всего, что мы знаем. С помощью List мы можем быть немного более уверены в том, как он будет работать, но как JIT может отличать эти реализации друг от друга?

  • Любой вызов метода в цикле потенциально может изменить возвращаемое значение Count (а не просто прямое «Добавить» "непосредственно в коллекции, но пользовательский метод, который вызывается в цикле, может также участвовать в коллекции)

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

JIT просто не может «знать», что Count постоянно.

Однако JIT-компилятор может сделать код более эффективным, встраивая реализацию свойства Count (пока это тривиальная реализация). В вашем примере он вполне может быть встроен в простой тест значения переменной, позволяющий избежать накладных расходов на вызов функции на каждой итерации и, таким образом, сделать окончательный код красивым и быстрым. (Примечание: я не знаю, сделает ли это JIT , просто она может . Мне все равно - посмотрите последнее предложение моего ответа, чтобы узнать, почему )

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

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

Следовательно, ИМХО, интересно рассматривать / понимать такие случаи, но в конечном итоге вам и не нужно знать. Немного знаний может быть опасным. Просто позвольте JIT делать свое дело, а затем профилируйте результат, чтобы увидеть, нуждается ли он в улучшении.

4
ответ дан 2 December 2019 в 03:48
поделиться

Нет , это не так. Потому что условие рассчитывается на каждом шаге. Это может быть сложнее, чем просто сравнение с count, и разрешено любое логическое выражение:

 for(int i = 0; new Random().NextDouble() < .5d; i++)
     Console.WriteLine(i);

http://msdn.microsoft.com/en-us/library/aa664753 (VS.71) .aspx

0
ответ дан 2 December 2019 в 03:48
поделиться

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

В этом случае вы можете сэкономить на вводе текста с помощью foreach.

List<int> list = new List<int>(){0};
foreach (int item in list)
{ 
    // ...
} 
0
ответ дан 2 December 2019 в 03:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: