From 2c376fc46cd01b12e003a7bf83d82f527f6efaf1 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Fri, 28 Jan 2011 17:34:01 -0800 Subject: [PATCH] Optimize IntentResolver to reduce lookup time by 50%. IntentResolver frequently iterates over hundreds of different IntentFilters and spends much of its time creating iterators and comparing strings. This change avoids reduces the amount of garbage created by eschewing iterators where possible. The FastImmutableArraySet type on its own provides a 2.5x speed boost compared to repeatedly iterating over a HashSet. In absolute terms, during orientation changes we spent about 160ms resolving 11 intents and performing 1129 calls to IntentFilter.match. Now we spend half of that time. Change-Id: Ia120e0082c8cf0b572a0317b9ef4a22c766dbad6 --- core/java/android/content/Intent.java | 16 ++-- core/java/android/content/IntentFilter.java | 40 +++++----- .../android/util/FastImmutableArraySet.java | 78 +++++++++++++++++++ .../com/android/server/IntentResolver.java | 31 +++++--- 4 files changed, 126 insertions(+), 39 deletions(-) create mode 100644 core/java/android/util/FastImmutableArraySet.java diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6e3663e341d52..e7b96e7f40986 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2794,7 +2794,7 @@ public class Intent implements Parcelable, Cloneable { * @param action The Intent action, such as ACTION_VIEW. */ public Intent(String action) { - mAction = action; + setAction(action); } /** @@ -2814,7 +2814,7 @@ public class Intent implements Parcelable, Cloneable { * @param uri The Intent data URI. */ public Intent(String action, Uri uri) { - mAction = action; + setAction(action); mData = uri; } @@ -2863,7 +2863,7 @@ public class Intent implements Parcelable, Cloneable { */ public Intent(String action, Uri uri, Context packageContext, Class cls) { - mAction = action; + setAction(action); mData = uri; mComponent = new ComponentName(packageContext, cls); } @@ -2985,7 +2985,7 @@ public class Intent implements Parcelable, Cloneable { // action if (uri.startsWith("action=", i)) { - intent.mAction = value; + intent.setAction(value); } // categories @@ -4061,7 +4061,7 @@ public class Intent implements Parcelable, Cloneable { * @see #getAction */ public Intent setAction(String action) { - mAction = action; + mAction = action != null ? action.intern() : null; return this; } @@ -4165,7 +4165,7 @@ public class Intent implements Parcelable, Cloneable { if (mCategories == null) { mCategories = new HashSet(); } - mCategories.add(category); + mCategories.add(category.intern()); return this; } @@ -5678,7 +5678,7 @@ public class Intent implements Parcelable, Cloneable { } public void readFromParcel(Parcel in) { - mAction = in.readString(); + setAction(in.readString()); mData = Uri.CREATOR.createFromParcel(in); mType = in.readString(); mFlags = in.readInt(); @@ -5694,7 +5694,7 @@ public class Intent implements Parcelable, Cloneable { mCategories = new HashSet(); int i; for (i=0; i i = mDataPaths.iterator(); - while (i.hasNext()) { - final PatternMatcher pe = i.next(); + final int numDataPaths = mDataPaths.size(); + for (int i = 0; i < numDataPaths; i++) { + final PatternMatcher pe = mDataPaths.get(i); if (pe.match(data)) { return true; } @@ -849,9 +845,9 @@ public class IntentFilter implements Parcelable { if (mDataAuthorities == null) { return NO_MATCH_DATA; } - Iterator i = mDataAuthorities.iterator(); - while (i.hasNext()) { - final AuthorityEntry ae = i.next(); + final int numDataAuthorities = mDataAuthorities.size(); + for (int i = 0; i < numDataAuthorities; i++) { + final AuthorityEntry ae = mDataAuthorities.get(i); int match = ae.match(data); if (match >= 0) { return match; @@ -1098,7 +1094,7 @@ public class IntentFilter implements Parcelable { */ public final int match(String action, String type, String scheme, Uri data, Set categories, String logTag) { - if (action != null && !matchAction(action)) { + if (!matchAction(action)) { if (Config.LOGV) Log.v( logTag, "No matching action " + action + " for " + this); return NO_MATCH_ACTION; @@ -1119,11 +1115,11 @@ public class IntentFilter implements Parcelable { return dataMatch; } - String categoryMatch = matchCategories(categories); - if (categoryMatch != null) { - if (Config.LOGV) Log.v( - logTag, "No matching category " - + categoryMatch + " for " + this); + String categoryMismatch = matchCategories(categories); + if (categoryMismatch != null) { + if (Config.LOGV) { + Log.v(logTag, "No matching category " + categoryMismatch + " for " + this); + } return NO_MATCH_CATEGORY; } @@ -1469,9 +1465,9 @@ public class IntentFilter implements Parcelable { if (typeLength == slashpos+2 && type.charAt(slashpos+1) == '*') { // Need to look through all types for one that matches // our base... - final Iterator it = t.iterator(); - while (it.hasNext()) { - String v = it.next(); + final int numTypes = t.size(); + for (int i = 0; i < numTypes; i++) { + final String v = t.get(i); if (type.regionMatches(0, v, 0, slashpos+1)) { return true; } diff --git a/core/java/android/util/FastImmutableArraySet.java b/core/java/android/util/FastImmutableArraySet.java new file mode 100644 index 0000000000000..4175c6057ecf7 --- /dev/null +++ b/core/java/android/util/FastImmutableArraySet.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2011 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 java.util.AbstractSet; +import java.util.Iterator; + +/** + * A fast immutable set wrapper for an array that is optimized for non-concurrent iteration. + * The same iterator instance is reused each time to avoid creating lots of garbage. + * Iterating over an array in this fashion is 2.5x faster than iterating over a {@link HashSet} + * so it is worth copying the contents of the set to an array when iterating over it + * hundreds of times. + * @hide + */ +public final class FastImmutableArraySet extends AbstractSet { + FastIterator mIterator; + T[] mContents; + + public FastImmutableArraySet(T[] contents) { + mContents = contents; + } + + @Override + public Iterator iterator() { + FastIterator it = mIterator; + if (it == null) { + it = new FastIterator(mContents); + mIterator = it; + } else { + it.mIndex = 0; + } + return it; + } + + @Override + public int size() { + return mContents.length; + } + + private static final class FastIterator implements Iterator { + private final T[] mContents; + int mIndex; + + public FastIterator(T[] contents) { + mContents = contents; + } + + @Override + public boolean hasNext() { + return mIndex != mContents.length; + } + + @Override + public T next() { + return mContents[mIndex++]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index a8b28403ee1a6..e9ee12ca2b8be 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -27,6 +27,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import android.net.Uri; +import android.util.FastImmutableArraySet; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -207,10 +209,11 @@ public class IntentResolver { final boolean debug = localLOGV || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); + FastImmutableArraySet categories = getFastIntentCategories(intent); final String scheme = intent.getScheme(); int N = listCut.size(); for (int i = 0; i < N; ++i) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, listCut.get(i), resultList); } sortResults(resultList); @@ -286,20 +289,21 @@ public class IntentResolver { if (debug) Slog.v(TAG, "Action list: " + firstTypeCut); } + FastImmutableArraySet categories = getFastIntentCategories(intent); if (firstTypeCut != null) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, firstTypeCut, finalList); } if (secondTypeCut != null) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, secondTypeCut, finalList); } if (thirdTypeCut != null) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, thirdTypeCut, finalList); } if (schemeCut != null) { - buildResolveList(intent, debug, defaultOnly, + buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, schemeCut, finalList); } sortResults(finalList); @@ -478,9 +482,19 @@ public class IntentResolver { return false; } - private void buildResolveList(Intent intent, boolean debug, boolean defaultOnly, + private static FastImmutableArraySet getFastIntentCategories(Intent intent) { + final Set categories = intent.getCategories(); + if (categories == null) { + return null; + } + return new FastImmutableArraySet(categories.toArray(new String[categories.size()])); + } + + private void buildResolveList(Intent intent, FastImmutableArraySet categories, + boolean debug, boolean defaultOnly, String resolvedType, String scheme, List src, List dest) { - Set categories = intent.getCategories(); + final String action = intent.getAction(); + final Uri data = intent.getData(); final int N = src != null ? src.size() : 0; boolean hasNonDefaults = false; @@ -498,8 +512,7 @@ public class IntentResolver { continue; } - match = filter.match( - intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG); + match = filter.match(action, resolvedType, scheme, data, categories, TAG); if (match >= 0) { if (debug) Slog.v(TAG, " Filter matched! match=0x" + Integer.toHexString(match));