Никому не доверяй, все сам проверяй

Для сравнения напишем несколько вариантов итерации. Чтобы компилятор не выкидывал бесполезный код, добавим подсчет суммы чисел:

public class Test {
	
	public static int o0(final int[] arr) {
		int s = 0;
		for (final int i : arr) {
			s += i;
		}
		return s;
	}
	
	public static int o1(final int[] arr) {
		int s = 0;
		for (int i = 0; i < arr.length; i++) {
			s += arr[i];
		}
		return s;
	}
	
	public static int o2(final int[] arr) {
		int s = 0;
		for (int i = 0, sz = arr.length; i < sz; i++) {
			s += arr[i];
		}
		return s;
	}
	
	public static int o3(final int[] arr) {
		int s = 0;
		for (int i = arr.length - 1; i >= 0; i--) {
			s += arr[i];
		}
		return s;
	}
	
	public static int o4(final int[] arr) {
		int s = 0;
		int i = arr.length - 1;
		for (; ; ) {
			s += arr[i];
			i--;
			if (i < 0) {
				break;
			}
		}
		return s;
	}
	
	public static int o5(final int[] arr) {
		int s = 0;
		int i = arr.length - 1;
		while (i >= 0) {
			s += arr[i];
			i--;
		}
		return s;
	}
	
}

Скомпилируем со следующими версиями:

targetSdkVersion 27
minSdkVersion 16
compileSdkVersion 28
buildToolsVersion '28.0.2'
classpath 'com.android.tools.build:gradle:3.1.3'
minifyEnabled false
shrinkResources false
debuggable true
android.enableD8=true

Посмотрим байт-код из получившегося apk:

.class public Lru/ivi/client/screensimpl/downloadscatalogserial/Test;
.super Ljava/lang/Object;
.source "Test.java"


# direct methods
.method public constructor <init>()V
    .registers 1

    .line 3
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method

.method public static o0([I)I
    .registers 5
    .param p0, "arr"    # [I

    .line 6
    const/4 v0, 0x0

    .line 7
    .local v0, "s":I
    array-length v1, p0

    const/4 v2, 0x0

    :goto_3
    if-ge v2, v1, :cond_b

    aget v3, p0, v2

    .line 8
    .local v3, "i":I
    add-int/2addr v0, v3

    .line 7
    .end local v3    # "i":I
    add-int/lit8 v2, v2, 0x1

    goto :goto_3

    .line 10
    :cond_b
    return v0
.end method

.method public static o1([I)I
    .registers 4
    .param p0, "arr"    # [I

    .line 14
    const/4 v0, 0x0

    .line 15
    .local v0, "s":I
    const/4 v1, 0x0

    .line 15
    .local v1, "i":I
    :goto_2
    array-length v2, p0

    if-ge v1, v2, :cond_b

    .line 16
    aget v2, p0, v1

    add-int/2addr v0, v2

    .line 15
    add-int/lit8 v1, v1, 0x1

    goto :goto_2

    .line 18
    .end local v1    # "i":I
    :cond_b
    return v0
.end method

.method public static o2([I)I
    .registers 5
    .param p0, "arr"    # [I

    .line 22
    const/4 v0, 0x0

    .line 23
    .local v0, "s":I
    const/4 v1, 0x0

    .line 23
    .local v1, "i":I
    array-length v2, p0

    .line 23
    .local v2, "sz":I
    :goto_3
    if-ge v1, v2, :cond_b

    .line 24
    aget v3, p0, v1

    add-int/2addr v0, v3

    .line 23
    add-int/lit8 v1, v1, 0x1

    goto :goto_3

    .line 26
    .end local v1    # "i":I
    .end local v2    # "sz":I
    :cond_b
    return v0
.end method

.method public static o3([I)I
    .registers 4
    .param p0, "arr"    # [I

    .line 30
    const/4 v0, 0x0

    .line 31
    .local v0, "s":I
    array-length v1, p0

    add-int/lit8 v1, v1, -0x1

    .line 31
    .local v1, "i":I
    :goto_4
    if-ltz v1, :cond_c

    .line 32
    aget v2, p0, v1

    add-int/2addr v0, v2

    .line 31
    add-int/lit8 v1, v1, -0x1

    goto :goto_4

    .line 34
    .end local v1    # "i":I
    :cond_c
    return v0
.end method

.method public static o4([I)I
    .registers 4
    .param p0, "arr"    # [I

    .line 38
    const/4 v0, 0x0

    .line 39
    .local v0, "s":I
    array-length v1, p0

    add-int/lit8 v1, v1, -0x1

    .line 41
    .local v1, "i":I
    :cond_4
    aget v2, p0, v1

    add-int/2addr v0, v2

    .line 42
    add-int/lit8 v1, v1, -0x1

    .line 43
    if-gez v1, :cond_4

    .line 44
    nop

    .line 47
    return v0
.end method

.method public static o5([I)I
    .registers 4
    .param p0, "arr"    # [I

    .line 51
    const/4 v0, 0x0

    .line 52
    .local v0, "s":I
    array-length v1, p0

    add-int/lit8 v1, v1, -0x1

    .line 53
    .local v1, "i":I
    :goto_4
    if-ltz v1, :cond_c

    .line 54
    aget v2, p0, v1

    add-int/2addr v0, v2

    .line 55
    add-int/lit8 v1, v1, -0x1

    goto :goto_4

    .line 57
    :cond_c
    return v0
.end method

Померяем “на глаз” (через SystemClock.elapsedRealtimeNanos) на устройстве OnePlus 3T (Android 8, API 26, Snapdragon 835, 6Gb). На каждый запуск выделяется отдельный массив размером 100_000_000. Действие происходит в главном потоке. Запустим 10 раз, смотрим на среднее, минимальное, максимальное и суммарное время. (наносекунды)

ox avg min max count sum
o0 188098202 141603698 531154323 10 1880982029
o1 169045885 133646093 469794063 10 1690458854
o2 223643875 174188073 631305521 10 2236438751
o3 220880307 173801302 596273073 10 2208803071
o4 230323713 175824167 679445365 10 2303237132
o5 220798156 173832551 550448177 10 2207981563

Победитель - o1, не кеширующий размер массива. Не сильно отстает от него o0 использующий enhanced-loop запись.

o5 и o3 приблизительно равны. На последних местах o2 и o4.

Неожиданно.

Попробуем Megafon Login (Android 4.4.2, MediaTek MT6582, 1Gb). Создать массив из 100млн интов на нем не удалось из-за OOM, поэтому только 1млн.

ox avg min max count sum
o0 57187707 53497769 62039923 10 571877076
o1 136767115 126513538 176206077 10 1367671153
o2 59381607 53819615 81372308 10 593816078
o3 58443446 54400077 66102384 10 584434461
o4 59261446 53455846 73940461 10 592614463
o5 60955246 53510924 80854770 10 609552461

Немного другой расклад: В лидерах остался o0 enhanced-loop. На одном уровне с ним o3, o4, o2 и o5. Сильно проиграл всем o1 - метод без кеширования array.length.

Наконец, пирог 2018 - Pixel XL (Android P, API 29). Размер массива 1млн.

ox avg min max count sum
o0 24985637 16415261 99575687 10 249856378
o1 26105393 16737970 107695114 10 261053932
o2 33134024 17729273 104561312 10 331340242
o3 81853331 41240004 133074909 10 818533314
o4 56166797 47559536 95691885 10 561667976
o5 42000582 34823546 90243446 10 420005824

Лидер - enhanced-loop o0, рядом с ним o1 с некешированным array.length. Середнячки - o2, o5. Чуть медленнее o4. Совсем плохи дела у o3, проходящего массив в обратном порядке.

Похоже на оптимизацию, видимо ART распознает только обычный цикл for.

Выводы

Как видим в байткоде есть разница между сравнением с переменной и сравнением с нулем, в одном случае if-ge, в другом if-ltz, if-gez. Но основную заметную разницу (в Android 4) делает вызов array-length.

В целом рекомендация использовать enhanced-loop актуальна для всех поддерживаемых android-API.