Одна маленькая оптимизация

в 10:13, , рубрики: iterator, java, метки: ,

Совсем недавно со мной поделились историей одной оптимизации (привет stanislaw), которая показалась мне довольно забавной.

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

for (A a : arrayListA) { 
    // do something
    for (B b : arrayListB) {
        // do something
        for (C c : arrayListC) {
            // do something
        }
    }
}

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

image

И при частых вызовах метода сборка занимала довольно большую часть времени работы метода.

Проблема оказалась в for циклах. Как известно, компилятор преобразовывает for цикл для коллекций в следующий код:

for (Iterator<A> i = arrayListA.iterator(); i.hasNext();) {
    a = i.next();
}

//смотрим во внутрь метода iterator() ArrayList
public Iterator<E> iterator() {
    return new Itr();
}

//и сам класс итератора. поля которые потребляют память
private class Itr implements Iterator<E> {
	int cursor = 0;
	int lastRet = -1;
	int expectedModCount = modCount;
}

Все бы хорошо, но если у вас несколько вложенных циклов, при чем короткие циклы на самом нижнем уровне вложения, то получается, что просто один лишь проход по циклам создаст тысячи объектов, а если метод постоянно вызывается, то еще и постоянные срабатывания сборщика.
Например, для первого примера — в случае если arrayListA.size() == 1000, arrayListB.size() == 100, arrayListC.size() == 10 за один проход по циклам будет создано около 100 тыс. объектов итераторов…

Решение тут довольно очевидно — получать доступ по индексу:

for (int i = 0; i < arrayListA.size(); i++) {
    A a = arrayListA.get(i);
}

Такое вот маленькое изменение кода в данном конкретном случае позволило увеличить скорость работы горячего метода в 2 раза.
Стоит отметить, что такого рода оптимизация возможна в основном в случае ArrayList.

Будьте внимательны и с прошедшим Вас Новым годом!

Автор: doom369

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js