Merge "Kill the StringBuilderPool."

This commit is contained in:
Jean Chalard 2012-03-13 02:14:37 -07:00 committed by Android (Google) Code Review
commit b8753eb31c
3 changed files with 11 additions and 117 deletions

View file

@ -1,70 +0,0 @@
/*
* 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 com.android.inputmethod.latin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A pool of string builders to be used from anywhere.
*/
public class StringBuilderPool {
// Singleton
private static final StringBuilderPool sInstance = new StringBuilderPool();
private static final boolean DEBUG = false;
private StringBuilderPool() {}
// TODO: Make this a normal array with a size of 20, or a ConcurrentQueue
private final List<StringBuilder> mPool =
Collections.synchronizedList(new ArrayList<StringBuilder>());
public static StringBuilder getStringBuilder(final int initialSize) {
// TODO: although the pool is synchronized, the following is not thread-safe.
// Two threads entering this at the same time could take the same size of the pool and the
// second to attempt removing this index from the pool would crash with an
// IndexOutOfBoundsException.
// At the moment this pool is only used in Suggest.java and only in one thread so it's
// okay. The simplest thing to do here is probably to replace the ArrayList with a
// ConcurrentQueue.
final int poolSize = sInstance.mPool.size();
final StringBuilder sb = poolSize > 0 ? (StringBuilder) sInstance.mPool.remove(poolSize - 1)
: new StringBuilder(initialSize);
sb.setLength(0);
return sb;
}
public static void recycle(final StringBuilder garbage) {
if (DEBUG) {
final int gid = garbage.hashCode();
for (final StringBuilder q : sInstance.mPool) {
if (gid == q.hashCode()) throw new RuntimeException("Duplicate id " + gid);
}
}
sInstance.mPool.add(garbage);
}
public static void ensureCapacity(final int capacity, final int initialSize) {
for (int i = sInstance.mPool.size(); i < capacity; ++i) {
final StringBuilder sb = new StringBuilder(initialSize);
sInstance.mPool.add(sb);
}
}
public static int getSize() {
return sInstance.mPool.size();
}
}

View file

@ -142,7 +142,7 @@ public class StringUtils {
for (int j = 0; j < i; j++) { for (int j = 0; j < i; j++) {
CharSequence previous = suggestions.get(j); CharSequence previous = suggestions.get(j);
if (TextUtils.equals(cur, previous)) { if (TextUtils.equals(cur, previous)) {
removeFromSuggestions(suggestions, i); suggestions.remove(i);
i--; i--;
break; break;
} }
@ -151,14 +151,6 @@ public class StringUtils {
} }
} }
private static void removeFromSuggestions(final ArrayList<CharSequence> suggestions,
final int index) {
final CharSequence garbage = suggestions.remove(index);
if (garbage instanceof StringBuilder) {
StringBuilderPool.recycle((StringBuilder)garbage);
}
}
public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) { public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
if (returnsNameInThisLocale) { if (returnsNameInThisLocale) {
return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale); return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);

View file

@ -98,7 +98,7 @@ public class Suggest implements Dictionary.WordCallback {
private int[] mBigramScores = new int[PREF_MAX_BIGRAMS]; private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>(); private ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
private CharSequence mConsideredWord; private CharSequence mConsideredWord;
// TODO: Remove these member variables by passing more context to addWord() callback method // TODO: Remove these member variables by passing more context to addWord() callback method
@ -122,7 +122,6 @@ public class Suggest implements Dictionary.WordCallback {
private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) { private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
mWhiteListDictionary = new WhitelistDictionary(context, locale); mWhiteListDictionary = new WhitelistDictionary(context, locale);
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary); addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
} }
private void initAsynchronously(final Context context, final int dictionaryResId, private void initAsynchronously(final Context context, final int dictionaryResId,
@ -229,14 +228,13 @@ public class Suggest implements Dictionary.WordCallback {
mPrefMaxSuggestions = maxSuggestions; mPrefMaxSuggestions = maxSuggestions;
mScores = new int[mPrefMaxSuggestions]; mScores = new int[mPrefMaxSuggestions];
mBigramScores = new int[PREF_MAX_BIGRAMS]; mBigramScores = new int[PREF_MAX_BIGRAMS];
collectGarbage(mSuggestions, mPrefMaxSuggestions); mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
} }
private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) { private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
if (TextUtils.isEmpty(word) || !(all || first)) return word; if (TextUtils.isEmpty(word) || !(all || first)) return word;
final int wordLength = word.length(); final int wordLength = word.length();
final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength()); final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
// TODO: Must pay attention to locale when changing case. // TODO: Must pay attention to locale when changing case.
if (all) { if (all) {
sb.append(word.toString().toUpperCase()); sb.append(word.toString().toUpperCase());
@ -250,12 +248,7 @@ public class Suggest implements Dictionary.WordCallback {
} }
protected void addBigramToSuggestions(CharSequence bigram) { protected void addBigramToSuggestions(CharSequence bigram) {
// TODO: Try to be a little more shrewd with resource allocation. final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
// At the moment we copy this object because the StringBuilders are pooled (see
// StringBuilderPool.java) and when we are finished using mSuggestions and
// mBigramSuggestions we will take everything from both and insert them back in the
// pool, so we can't allow the same object to be in both lists at the same time.
final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
sb.append(bigram); sb.append(bigram);
mSuggestions.add(sb); mSuggestions.add(sb);
} }
@ -266,7 +259,7 @@ public class Suggest implements Dictionary.WordCallback {
mIsFirstCharCapitalized = false; mIsFirstCharCapitalized = false;
mIsAllUpperCase = false; mIsAllUpperCase = false;
mTrailingSingleQuotesCount = 0; mTrailingSingleQuotesCount = 0;
collectGarbage(mSuggestions, mPrefMaxSuggestions); mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
Arrays.fill(mScores, 0); Arrays.fill(mScores, 0);
// Treating USER_TYPED as UNIGRAM suggestion for logging now. // Treating USER_TYPED as UNIGRAM suggestion for logging now.
@ -274,7 +267,7 @@ public class Suggest implements Dictionary.WordCallback {
mConsideredWord = ""; mConsideredWord = "";
Arrays.fill(mBigramScores, 0); Arrays.fill(mBigramScores, 0);
collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS); mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase(); CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) { if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
@ -305,7 +298,7 @@ public class Suggest implements Dictionary.WordCallback {
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase(); mIsAllUpperCase = wordComposer.isAllUpperCase();
mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
collectGarbage(mSuggestions, mPrefMaxSuggestions); mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
Arrays.fill(mScores, 0); Arrays.fill(mScores, 0);
final String typedWord = wordComposer.getTypedWord(); final String typedWord = wordComposer.getTypedWord();
@ -328,7 +321,7 @@ public class Suggest implements Dictionary.WordCallback {
if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) { if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
// At first character typed, search only the bigrams // At first character typed, search only the bigrams
Arrays.fill(mBigramScores, 0); Arrays.fill(mBigramScores, 0);
collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS); mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
if (!TextUtils.isEmpty(prevWordForBigram)) { if (!TextUtils.isEmpty(prevWordForBigram)) {
CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase(); CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
@ -542,7 +535,7 @@ public class Suggest implements Dictionary.WordCallback {
System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1); System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
sortedScores[pos] = score; sortedScores[pos] = score;
final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength()); final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
// TODO: Must pay attention to locale when changing case. // TODO: Must pay attention to locale when changing case.
if (mIsAllUpperCase) { if (mIsAllUpperCase) {
sb.append(new String(word, offset, length).toUpperCase()); sb.append(new String(word, offset, length).toUpperCase());
@ -559,10 +552,7 @@ public class Suggest implements Dictionary.WordCallback {
} }
suggestions.add(pos, sb); suggestions.add(pos, sb);
if (suggestions.size() > prefMaxSuggestions) { if (suggestions.size() > prefMaxSuggestions) {
final CharSequence garbage = suggestions.remove(prefMaxSuggestions); suggestions.remove(prefMaxSuggestions);
if (garbage instanceof StringBuilder) {
StringBuilderPool.recycle((StringBuilder)garbage);
}
} else { } else {
LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog); LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
} }
@ -589,24 +579,6 @@ public class Suggest implements Dictionary.WordCallback {
return -1; return -1;
} }
private static void collectGarbage(ArrayList<CharSequence> suggestions,
int prefMaxSuggestions) {
int poolSize = StringBuilderPool.getSize();
int garbageSize = suggestions.size();
while (poolSize < prefMaxSuggestions && garbageSize > 0) {
final CharSequence garbage = suggestions.get(garbageSize - 1);
if (garbage instanceof StringBuilder) {
StringBuilderPool.recycle((StringBuilder)garbage);
poolSize++;
}
garbageSize--;
}
if (poolSize == prefMaxSuggestions + 1) {
Log.w("Suggest", "String pool got too big: " + poolSize);
}
suggestions.clear();
}
public void close() { public void close() {
final Set<Dictionary> dictionaries = new HashSet<Dictionary>(); final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
dictionaries.addAll(mUnigramDictionaries.values()); dictionaries.addAll(mUnigramDictionaries.values());