Skip to content

Commit 3232907

Browse files
authored
Apply recent C optimizations to Java encoder (#725)
* Make benchmark runnable without oj available * Port convert_UTF8_to_ASCII_only_JSON to Java This is new specialized logic to reduce overhead when appending ASCII-only strings to the generated JSON. Original code by @byroot See #620 * Align string generate method with generate_json_string * Port convert_UTF8_to_JSON from C Also includes updated logic for generate (generate_json_string) based on current C code. Original code by @byroot See #620 * Use external iteration to reduce alloc Lots of surrounding state so just take the hit of a Set and Iterator rather than a big visitor object. * Remove unused imports * Inline ConvertBytes logic for long to byte[] This change duplicates some code from JRuby to allow rendering the fixnum value to a shared byte array rather than allocating new for each value. Since fixnum dumping is a leaf operation, only one is needed per session. * Eliminate * import * Restructure handlers for easier profiling Anonymous classes show up as unnamed, numbered classes in profiles which makes them difficult to read. * Avoid allocation when writing Array delimiters Rather than allocating a buffer to hold N copies of arrayNL, just write it N times. We're buffering into a stream anyway. This makes array dumping zero-alloc other than buffer growth. * Move away from Handler abstraction Since there's a fixed number of types we have special dumping logic for, this abstraction just introduces overhead we don't need. This patch starts moving away from indirecting all dumps through the Handler abstraction and directly generating from the type switch. This also aligns better with the main loop of the C code and should inline and optimize better. * Match C version of fbuffer_append_long * Minor tweaks to reduce complexity * Reimpl byte[] stream without synchronization The byte[] output stream used here extended ByteArrayOutputStream from the JDK, which sychronizes all mutation operations (like writes). Since this is only going to be used once within a given call stack, it needs no synchronization. This change more than triples the performance of a benchmark of dumping an array of empty arrays and should increase performance of all dump forms. * Reduce overhead in repeats * Return incoming array if only one repeat is needed and array is exact size. * Only retrieve ByteList fields once for repeat writes. * Use equivalent of rb_sym2str * Microoptimizations for ByteList stream * Cast to byte not necessary * Refactor this for better inlining * More tiny tweaks to reduce overhead of generateString * Refactor to avoid repeated boolean checks * Eliminate memory accesses for digits The math is much faster here than array access, due to bounds checking and pointer dereferencing. * Loosen visibility to avoid accessor methods Java will generated accessor methods for private fields, burning some inlining budget. * Modify parser bench to work without oj or rapidjson
1 parent c84daef commit 3232907

7 files changed

+830
-423
lines changed

java/src/json/ext/ByteListDirectOutputStream.java

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,72 @@
33
import org.jcodings.Encoding;
44
import org.jruby.util.ByteList;
55

6-
import java.io.ByteArrayOutputStream;
6+
import java.io.IOException;
7+
import java.io.OutputStream;
8+
import java.util.Arrays;
9+
10+
public class ByteListDirectOutputStream extends OutputStream {
11+
private byte[] buffer;
12+
private int length;
713

8-
public class ByteListDirectOutputStream extends ByteArrayOutputStream {
914
ByteListDirectOutputStream(int size) {
10-
super(size);
15+
buffer = new byte[size];
1116
}
1217

1318
public ByteList toByteListDirect(Encoding encoding) {
14-
return new ByteList(buf, 0, count, encoding, false);
19+
return new ByteList(buffer, 0, length, encoding, false);
20+
}
21+
22+
@Override
23+
public void write(int b) throws IOException {
24+
int currentLength = this.length;
25+
int newLength = currentLength + 1;
26+
byte[] buffer = ensureBuffer(this, newLength);
27+
buffer[currentLength] = (byte) b;
28+
this.length = newLength;
29+
}
30+
31+
@Override
32+
public void write(byte[] bytes, int start, int length) throws IOException {
33+
int currentLength = this.length;
34+
int newLength = currentLength + length;
35+
byte[] buffer = ensureBuffer(this, newLength);
36+
System.arraycopy(bytes, start, buffer, currentLength, length);
37+
this.length = newLength;
38+
}
39+
40+
@Override
41+
public void write(byte[] bytes) throws IOException {
42+
int myLength = this.length;
43+
int moreLength = bytes.length;
44+
int newLength = myLength + moreLength;
45+
byte[] buffer = ensureBuffer(this, newLength);
46+
System.arraycopy(bytes, 0, buffer, myLength, moreLength);
47+
this.length = newLength;
48+
}
49+
50+
private static byte[] ensureBuffer(ByteListDirectOutputStream self, int minimumLength) {
51+
byte[] buffer = self.buffer;
52+
int myCapacity = buffer.length;
53+
int diff = minimumLength - myCapacity;
54+
if (diff > 0) {
55+
buffer = self.buffer = grow(buffer, myCapacity, diff);
56+
}
57+
58+
return buffer;
59+
}
60+
61+
private static byte[] grow(byte[] oldBuffer, int myCapacity, int diff) {
62+
// grow to double current buffer length or capacity + diff, whichever is greater
63+
int newLength = myCapacity + Math.max(myCapacity, diff);
64+
// check overflow
65+
if (newLength < 0) {
66+
// try just diff length in case it can fit
67+
newLength = myCapacity + diff;
68+
if (newLength < 0) {
69+
throw new ArrayIndexOutOfBoundsException("cannot allocate array of size " + myCapacity + "+" + diff);
70+
}
71+
}
72+
return Arrays.copyOf(oldBuffer, newLength);
1573
}
1674
}

java/src/json/ext/ByteListTranscoder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,11 @@ protected void quoteStart() {
143143
* until the character before it.
144144
*/
145145
protected void quoteStop(int endPos) throws IOException {
146+
int quoteStart = this.quoteStart;
146147
if (quoteStart != -1) {
148+
ByteList src = this.src;
147149
append(src.unsafeBytes(), src.begin() + quoteStart, endPos - quoteStart);
148-
quoteStart = -1;
150+
this.quoteStart = -1;
149151
}
150152
}
151153

0 commit comments

Comments
 (0)