Использует ли промежуточную переменную вместо array.length ускорить цикл for-loop?
"Рекомендации по производительности" в документации по Android имеет довольно смелое утверждение:
one()
работает быстрее. Он выводит все на локальные переменные, избегая поиска. Только длина массива дает преимущество в производительности.
где он ссылается на этот фрагмент кода:
int len = localArray.length;
for (int i = 0; i < len; ++i) {
sum += localArray[i].mSplat;
}
Это меня очень удивило, потому что localArray.length
просто обращается к целому числу, и если вы будете использовать промежуточную переменную, вам придется сделать то же самое еще раз. Действительно ли мы говорим, что промежуточная переменная, которая должна перейти только в x
вместо y.x
, быстрее?
Я рассмотрел этот вопрос, который касается той же идеи, но вместо этого использует arraylist и его последующий метод .size()
. Здесь консенсус, казалось, заключался в том, что не будет никакой разницы, так как этот вызов метода, вероятно, просто будет привязан к целочисленному доступу в любом случае (это именно тот сценарий, который у нас есть здесь).
Итак, я взял байт-код, чтобы узнать, может ли это что-нибудь сказать мне.
Учитывая следующий исходный код:
public void MethodOne() {
int[] arr = new int[5];
for (int i = 0; i < arr.length; i++) { }
}
public void MethodTwo() {
int[] arr = new int[5];
int len = arr.length;
for (int i = 0; i < len; i++) { }
}
Я получаю следующий байт-код:
public void MethodOne();
Code:
0: iconst_5
1: newarray int
3: astore_1
4: iconst_0
5: istore_2
6: iload_2
7: aload_1
8: arraylength
9: if_icmpge 18
12: iinc 2, 1
15: goto 6
18: return
public void MethodTwo();
Code:
0: iconst_5
1: newarray int
3: astore_1
4: aload_1
5: arraylength
6: istore_2
7: iconst_0
8: istore_3
9: iload_3
10: iload_2
11: if_icmpge 20
14: iinc 3, 1
17: goto 9
20: return
Они отличаются в следующих инструкциях:
Метод один
6: iload_2
7: aload_1
8: arraylength
9: if_icmpge 18
12: iinc 2, 1
15: goto 6
18: return
Метод два
9: iload_3
10: iload_2
11: if_icmpge 20
14: iinc 3, 1
17: goto 9
20: return
Теперь я не уверен на 100%, как мне нужно интерпретировать 8: arraylength
, но я думаю, что это просто указывает поле, к которому вы обращаетесь. Первый метод загружает индексный счетчик и массив и обращается к полю arraylength
, а второй метод загружает индексный счетчик и промежуточную переменную.
Я сравнивал два метода с JMH (10 разминок, 10 итераций, 5 вилок), что дает мне следующий результат сравнения:
c.m.m.Start.MethodOne thrpt 50 3447184.351 19973.900 ops/ms
c.m.m.Start.MethodTwo thrpt 50 3435112.281 32639.755 ops/ms
который говорит мне, что разница пренебрежимо мала.
В чем заключается заявка на документацию Android о том, что с использованием промежуточной переменной в состоянии цикла на основе?
Ответы
Ответ 1
Вы неправильно поняли документацию. Они не относятся к тому, что вы описали (хотя я не обвиняю вас, они должны приложить больше усилий в эти документы:)).
Он выводит все на локальные переменные, избегая поиска.
Избегая поиска, они ссылаются на поле и стоимость доступа к локальной переменной. Для доступа к полю (mArray
в примере в документах) сначала необходимо загрузить this
, а затем загрузить поле на основе фиксированного смещения от this
.
Через некоторое время JIT, вероятно, выяснит, что происходит и оптимизирует доступ к полю (если это поле не volatile
или какая-то другая форма синхронизации происходит в цикле) и переписать код так, чтобы все переменные, участвующие в цикле, получают доступ/изменяются в регистры процессора и кэша до конца цикла.
Как правило, для JIT может быть гораздо больше работы, чтобы выяснить, безопасно ли оптимизировать доступ к длине массива, на который ссылается поле, по сравнению с ссылкой, хранящейся в локальной переменной. Пусть говорят, что мы имеем следующий цикл:
for (int i = 0; i < array.length; ++i) {
process(array[i]);
}
Если array
- это поле, а process
вызывает тысячи строк сложного кода, то JIT может с трудом проверить, было ли поле array
изменено где-то в цикле, чтобы ссылаться на некоторый другой массив, который имеет разной длины.
Очевидно, гораздо проще проверить, изменяется ли локальная переменная в этом случае (три строки кода).
Ответ 2
Фактически нет, это не ускорило цикл, ваша идея правильная при использовании String.length()
разница в том, что
array.length - это просто поле, которое имеет значение, которое вы просто используете непосредственно.
String.length() - это метод, который требует времени для выполнения.