From 4049d0fd9ce283617079a341916cc423fed62c80 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Tue, 10 Aug 2021 08:31:30 -0700 Subject: [PATCH] Improve forEach. The default implementation of Collection.forEach() uses the iterator(). Switch to a simple for loop iterating through the array since the iterator is an inefficient way to access the array contents. Benchmark results: Before: android.util.ArrayMapPerfTest#testForEach_Large: forEach_Large_min (ns): 599759 forEach_Large_median (ns): 655845 forEach_Large_mean (ns): 636927 forEach_Large_standardDeviation: 34373 android.util.ArrayMapPerfTest#testForEach_Small: forEach_Small_min (ns): 118822 forEach_Small_median (ns): 121422 forEach_Small_mean (ns): 121695 forEach_Small_standardDeviation: 2079 After: android.util.ArrayMapPerfTest#testForEach_Large: forEach_Large_min (ns): 403562 forEach_Large_median (ns): 441364 forEach_Large_mean (ns): 442301 forEach_Large_standardDeviation: 24917 android.util.ArrayMapPerfTest#testForEach_Small: forEach_Small_min (ns): 81889 forEach_Small_median (ns): 87889 forEach_Small_mean (ns): 86905 forEach_Small_standardDeviation: 2880 Bug: 194098491 Test: atest CorePerfTests:ArrayMapPerfTest (see results above) Test: atest CtsUtilTestCases:ArrayMapTest Test: atest FrameworksCoreTests:ArrayMapTest Change-Id: Id528fe24f1d17af01a2d639753d42e2ad21144d2 --- .../src/android/util/ArrayMapPerfTest.java | 72 +++++++++++++++++++ core/java/android/util/ArrayMap.java | 23 ++++++ 2 files changed, 95 insertions(+) create mode 100644 apct-tests/perftests/core/src/android/util/ArrayMapPerfTest.java diff --git a/apct-tests/perftests/core/src/android/util/ArrayMapPerfTest.java b/apct-tests/perftests/core/src/android/util/ArrayMapPerfTest.java new file mode 100644 index 0000000000000..b93a6ea18d8db --- /dev/null +++ b/apct-tests/perftests/core/src/android/util/ArrayMapPerfTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.BiConsumer; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class ArrayMapPerfTest { + private static final int NUM_ITERATIONS = 100; + private static final int SET_SIZE_SMALL = 10; + private static final int SET_SIZE_LARGE = 50; + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Test + public void testForEach_Small() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + BiConsumer consumer = (s, i) -> { + }; + while (state.keepRunning()) { + for (int i = 0; i < NUM_ITERATIONS; ++i) { + ArrayMap map = new ArrayMap<>(); + for (int j = 0; j < SET_SIZE_SMALL; j++) { + map.put(Integer.toString(j), j); + } + map.forEach(consumer); + } + } + } + + @Test + public void testForEach_Large() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + BiConsumer consumer = (s, i) -> { + }; + while (state.keepRunning()) { + for (int i = 0; i < NUM_ITERATIONS; ++i) { + ArrayMap map = new ArrayMap<>(); + for (int j = 0; j < SET_SIZE_LARGE; j++) { + map.put(Integer.toString(j), j); + } + map.forEach(consumer); + } + } + } +} diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java index 4edff27d0ced4..f50b51bbdb356 100644 --- a/core/java/android/util/ArrayMap.java +++ b/core/java/android/util/ArrayMap.java @@ -27,6 +27,7 @@ import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; /** * ArrayMap is a generic key->value mapping data structure that is @@ -979,6 +980,28 @@ public final class ArrayMap implements Map { return MapCollections.containsAllHelper(this, collection); } + /** + * Performs the given action for all elements in the stored order. This implementation overrides + * the default implementation to avoid iterating using the {@link #entrySet()} and iterates in + * the key-value order consistent with {@link #keyAt(int)} and {@link #valueAt(int)}. + * + * @param action The action to be performed for each element + */ + @Override + public void forEach(BiConsumer action) { + if (action == null) { + throw new NullPointerException("action must not be null"); + } + + final int size = mSize; + for (int i = 0; i < size; ++i) { + if (size != mSize) { + throw new ConcurrentModificationException(); + } + action.accept(keyAt(i), valueAt(i)); + } + } + /** * Perform a {@link #put(Object, Object)} of all key/value pairs in map * @param map The map whose contents are to be retrieved.