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:
parent
680bd75a85
commit
084047d80b
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user