Reintroduce string pooling in JsonReader.

This makes Hotspot slower. From my before/after measurements using ParseBenchmark, times in microseconds:
  TWEETS: 350 -> 370 (+6%)
  READER_SHORT: 77 -> 76 (-1%)
  READER_LONG: 870 -> 940 (+8%)
  
But it makes Dalvik faster by a greater margin. These before/after measurements use times in milliseconds:
  TWEETS: 25 -> 20 (-20%)
  READER_SHORT: 5.6 -> 4.7 (-16%)
  READER_LONG: 52 -> 47 (-10%)
 
 It's a net win because we're saving a greater fraction of time, and because we're helping the platform that needs the most help. We're paying microseconds on Hotspot to gain milliseconds on Dalvik.
This commit is contained in:
Jesse Wilson 2012-09-10 16:13:33 +00:00
parent 680bd75a85
commit 084047d80b
2 changed files with 33 additions and 18 deletions

View File

@ -213,6 +213,16 @@ public class JsonReader implements Closeable {
private static final int PEEKED_NUMBER = 16; private static final int PEEKED_NUMBER = 16;
private static final int PEEKED_EOF = 17; private static final int PEEKED_EOF = 17;
/* State machine when parsing numbers */
private static final int NUMBER_CHAR_NONE = 0;
private static final int NUMBER_CHAR_SIGN = 1;
private static final int NUMBER_CHAR_DIGIT = 2;
private static final int NUMBER_CHAR_DECIMAL = 3;
private static final int NUMBER_CHAR_FRACTION_DIGIT = 4;
private static final int NUMBER_CHAR_EXP_E = 5;
private static final int NUMBER_CHAR_EXP_SIGN = 6;
private static final int NUMBER_CHAR_EXP_DIGIT = 7;
/** The input JSON. */ /** The input JSON. */
private final Reader in; private final Reader in;
@ -253,6 +263,11 @@ public class JsonReader implements Closeable {
*/ */
private String peekedString; private String peekedString;
/**
* A pool of short strings intended to prevent object allocation.
*/
private static final StringPool stringPool = new StringPool();
/* /*
* The nesting stack. Using a manual array rather than an ArrayList saves 20%. * The nesting stack. Using a manual array rather than an ArrayList saves 20%.
*/ */
@ -624,15 +639,6 @@ public class JsonReader implements Closeable {
return peeked = peeking; return peeked = peeking;
} }
private static final int NUMBER_CHAR_NONE = 0;
private static final int NUMBER_CHAR_SIGN = 1;
private static final int NUMBER_CHAR_DIGIT = 2;
private static final int NUMBER_CHAR_DECIMAL = 3;
private static final int NUMBER_CHAR_FRACTION_DIGIT = 4;
private static final int NUMBER_CHAR_EXP_E = 5;
private static final int NUMBER_CHAR_EXP_SIGN = 6;
private static final int NUMBER_CHAR_EXP_DIGIT = 7;
private int peekNumber() throws IOException { private int peekNumber() throws IOException {
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access. // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
char[] buffer = this.buffer; char[] buffer = this.buffer;
@ -974,6 +980,7 @@ public class JsonReader implements Closeable {
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access. // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
char[] buffer = this.buffer; char[] buffer = this.buffer;
StringBuilder builder = null; StringBuilder builder = null;
int hashCode = 0;
while (true) { while (true) {
int p = pos; int p = pos;
int l = limit; int l = limit;
@ -985,7 +992,7 @@ public class JsonReader implements Closeable {
if (c == quote) { if (c == quote) {
pos = p; pos = p;
if (builder == null) { if (builder == null) {
return new String(buffer, start, p - start - 1); return stringPool.get(buffer, start, p - start - 1, hashCode);
} else { } else {
builder.append(buffer, start, p - start - 1); builder.append(buffer, start, p - start - 1);
return builder.toString(); return builder.toString();
@ -1003,8 +1010,11 @@ public class JsonReader implements Closeable {
start = p; start = p;
} else if (c == '\n') { } else if (c == '\n') {
hashCode = (hashCode * 31) + c;
lineNumber++; lineNumber++;
lineStart = p; lineStart = p;
} else {
hashCode = (hashCode * 31) + c;
} }
} }

View File

@ -19,20 +19,25 @@ package com.google.gson.stream;
/** /**
* A pool of string instances. Unlike the {@link String#intern() VM's * A pool of string instances. Unlike the {@link String#intern() VM's
* interned strings}, this pool provides no guarantee of reference equality. * interned strings}, this pool provides no guarantee of reference equality.
* It is intended only to save allocations. This class is not thread safe. * It is intended only to save allocations.
*
* <p>This class is safe for concurrent use.
*/ */
final class StringPool { final class StringPool {
/**
private final String[] pool = new String[512]; * The maximum length of strings to add to the pool. Strings longer than this
* don't benefit from pooling because we spend more time on pooling than we
* save on garbage collection.
*/
private static final int MAX_LENGTH = 20;
private final String[] pool = new String[1024];
/** /**
* Returns a string equal to {@code new String(array, start, length)}. * Returns a string equal to {@code new String(array, start, length)}.
*/ */
public String get(char[] array, int start, int length) { public String get(char[] array, int start, int length, int hashCode) {
// Compute an arbitrary hash of the content if (length > StringPool.MAX_LENGTH) {
int hashCode = 0; return new String(array, start, length);
for (int i = start; i < start + length; i++) {
hashCode = (hashCode * 31) + array[i];
} }
// Pick a bucket using Doug Lea's supplemental secondaryHash function (from HashMap) // Pick a bucket using Doug Lea's supplemental secondaryHash function (from HashMap)