Skip to content

Commit

Permalink
Reintroduce string pooling in JsonReader.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
swankjesse committed Sep 10, 2012
1 parent 680bd75 commit 084047d
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 18 deletions.
30 changes: 20 additions & 10 deletions gson/src/main/java/com/google/gson/stream/JsonReader.java
Expand Up @@ -213,6 +213,16 @@ public class JsonReader implements Closeable {
private static final int PEEKED_NUMBER = 16;
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. */
private final Reader in;

Expand Down Expand Up @@ -253,6 +263,11 @@ public class JsonReader implements Closeable {
*/
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%.
*/
Expand Down Expand Up @@ -624,15 +639,6 @@ && isLiteral(buffer[pos + length])) {
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 {
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
char[] buffer = this.buffer;
Expand Down Expand Up @@ -974,6 +980,7 @@ private String nextQuotedValue(char quote) throws IOException {
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
char[] buffer = this.buffer;
StringBuilder builder = null;
int hashCode = 0;
while (true) {
int p = pos;
int l = limit;
Expand All @@ -985,7 +992,7 @@ private String nextQuotedValue(char quote) throws IOException {
if (c == quote) {
pos = p;
if (builder == null) {
return new String(buffer, start, p - start - 1);
return stringPool.get(buffer, start, p - start - 1, hashCode);
} else {
builder.append(buffer, start, p - start - 1);
return builder.toString();
Expand All @@ -1003,8 +1010,11 @@ private String nextQuotedValue(char quote) throws IOException {
start = p;

} else if (c == '\n') {
hashCode = (hashCode * 31) + c;
lineNumber++;
lineStart = p;
} else {
hashCode = (hashCode * 31) + c;
}
}

Expand Down
21 changes: 13 additions & 8 deletions gson/src/main/java/com/google/gson/stream/StringPool.java
Expand Up @@ -19,20 +19,25 @@
/**
* A pool of string instances. Unlike the {@link String#intern() VM's
* 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 {

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)}.
*/
public String get(char[] array, int start, int length) {
// Compute an arbitrary hash of the content
int hashCode = 0;
for (int i = start; i < start + length; i++) {
hashCode = (hashCode * 31) + array[i];
public String get(char[] array, int start, int length, int hashCode) {
if (length > StringPool.MAX_LENGTH) {
return new String(array, start, length);
}

// Pick a bucket using Doug Lea's supplemental secondaryHash function (from HashMap)
Expand Down

0 comments on commit 084047d

Please sign in to comment.