Skip to content

Commit 084047d

Browse files
committed
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.
1 parent 680bd75 commit 084047d

File tree

2 files changed

+33
-18
lines changed

2 files changed

+33
-18
lines changed

gson/src/main/java/com/google/gson/stream/JsonReader.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ public class JsonReader implements Closeable {
213213
private static final int PEEKED_NUMBER = 16;
214214
private static final int PEEKED_EOF = 17;
215215

216+
/* State machine when parsing numbers */
217+
private static final int NUMBER_CHAR_NONE = 0;
218+
private static final int NUMBER_CHAR_SIGN = 1;
219+
private static final int NUMBER_CHAR_DIGIT = 2;
220+
private static final int NUMBER_CHAR_DECIMAL = 3;
221+
private static final int NUMBER_CHAR_FRACTION_DIGIT = 4;
222+
private static final int NUMBER_CHAR_EXP_E = 5;
223+
private static final int NUMBER_CHAR_EXP_SIGN = 6;
224+
private static final int NUMBER_CHAR_EXP_DIGIT = 7;
225+
216226
/** The input JSON. */
217227
private final Reader in;
218228

@@ -253,6 +263,11 @@ public class JsonReader implements Closeable {
253263
*/
254264
private String peekedString;
255265

266+
/**
267+
* A pool of short strings intended to prevent object allocation.
268+
*/
269+
private static final StringPool stringPool = new StringPool();
270+
256271
/*
257272
* The nesting stack. Using a manual array rather than an ArrayList saves 20%.
258273
*/
@@ -624,15 +639,6 @@ && isLiteral(buffer[pos + length])) {
624639
return peeked = peeking;
625640
}
626641

627-
private static final int NUMBER_CHAR_NONE = 0;
628-
private static final int NUMBER_CHAR_SIGN = 1;
629-
private static final int NUMBER_CHAR_DIGIT = 2;
630-
private static final int NUMBER_CHAR_DECIMAL = 3;
631-
private static final int NUMBER_CHAR_FRACTION_DIGIT = 4;
632-
private static final int NUMBER_CHAR_EXP_E = 5;
633-
private static final int NUMBER_CHAR_EXP_SIGN = 6;
634-
private static final int NUMBER_CHAR_EXP_DIGIT = 7;
635-
636642
private int peekNumber() throws IOException {
637643
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
638644
char[] buffer = this.buffer;
@@ -974,6 +980,7 @@ private String nextQuotedValue(char quote) throws IOException {
974980
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
975981
char[] buffer = this.buffer;
976982
StringBuilder builder = null;
983+
int hashCode = 0;
977984
while (true) {
978985
int p = pos;
979986
int l = limit;
@@ -985,7 +992,7 @@ private String nextQuotedValue(char quote) throws IOException {
985992
if (c == quote) {
986993
pos = p;
987994
if (builder == null) {
988-
return new String(buffer, start, p - start - 1);
995+
return stringPool.get(buffer, start, p - start - 1, hashCode);
989996
} else {
990997
builder.append(buffer, start, p - start - 1);
991998
return builder.toString();
@@ -1003,8 +1010,11 @@ private String nextQuotedValue(char quote) throws IOException {
10031010
start = p;
10041011

10051012
} else if (c == '\n') {
1013+
hashCode = (hashCode * 31) + c;
10061014
lineNumber++;
10071015
lineStart = p;
1016+
} else {
1017+
hashCode = (hashCode * 31) + c;
10081018
}
10091019
}
10101020

gson/src/main/java/com/google/gson/stream/StringPool.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,25 @@
1919
/**
2020
* A pool of string instances. Unlike the {@link String#intern() VM's
2121
* interned strings}, this pool provides no guarantee of reference equality.
22-
* It is intended only to save allocations. This class is not thread safe.
22+
* It is intended only to save allocations.
23+
*
24+
* <p>This class is safe for concurrent use.
2325
*/
2426
final class StringPool {
25-
26-
private final String[] pool = new String[512];
27+
/**
28+
* The maximum length of strings to add to the pool. Strings longer than this
29+
* don't benefit from pooling because we spend more time on pooling than we
30+
* save on garbage collection.
31+
*/
32+
private static final int MAX_LENGTH = 20;
33+
private final String[] pool = new String[1024];
2734

2835
/**
2936
* Returns a string equal to {@code new String(array, start, length)}.
3037
*/
31-
public String get(char[] array, int start, int length) {
32-
// Compute an arbitrary hash of the content
33-
int hashCode = 0;
34-
for (int i = start; i < start + length; i++) {
35-
hashCode = (hashCode * 31) + array[i];
38+
public String get(char[] array, int start, int length, int hashCode) {
39+
if (length > StringPool.MAX_LENGTH) {
40+
return new String(array, start, length);
3641
}
3742

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

0 commit comments

Comments
 (0)