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

Использование размера коллекции для сравнения циклов

Есть ли оптимизация компилятора для методов size() для Collections в Java?

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

for(int i=0;i<list.size();i++)
      ...some operation.....

Существует вызов метода size() для каждого i. Разве не лучше узнать размер и повторно использовать его? (У вызовов метода есть накладные расходы).

final int len = list.size()
for(int i=0;i<len;i++)
      ...some operation.....

Однако, когда я приурочил оба эти фрагмента кода, не было существенной разницы во времени, даже для я до 10000000. Я что-то пропустил?

Update1: Я понимаю, что размер не вычисляется снова, если коллекция не изменяется. Но должны быть некоторые накладные расходы, связанные с вызовом метода. Это тот случай, когда компилятор всегда строит их (см. Ответ Esko)?

Обновление 2: Мое любопытство подпитывается дальше. Из приведенных ответов я вижу, что хорошие компиляторы JIT часто связывают этот вызов функции. Но они все равно должны будут определить, была ли коллекция изменена или нет. Я не принимаю ответ в надежде, что кто-то даст мне указания относительно того, как это обрабатывается компиляторами.

4b9b3361

Ответ 1

Хорошо, вот выдержка из источников JDK (src.zip в папке JDK):

public int size() {
    return size;
}

Это из ArrayList, но я думаю, что другие коллекции имеют аналогичные реализации. Теперь, если мы предположим, что компилятор встраивает вызов size() (что будет иметь смысл), ваш цикл превращается в это:

for(int i=0;i<list.size;i++)
// ...

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

Что происходит, когда коллекция изменяется, скажем, методом add()?

public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

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

Ответ 2

Значение, возвращаемое методом collection .size(), обычно кэшируется и пересчитывается только тогда, когда изменяется фактическая коллекция (добавляются новые элементы или удаляются старые).

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

Ответ 3

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

Ответ 4

спецификация java language объясняет, что выражение оценивается на каждом этапе итерации. Например, list.size() называется 10.000.000 раз.

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