Pull to refresh

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

Reading time 2 min
Views 38K
Совсем недавно со мной поделились историей одной оптимизации (привет 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.

Будьте внимательны и с прошедшим Вас Новым годом!
Tags:
Hubs:
+72
Comments 99
Comments Comments 99

Articles