Skip to content

Commit 5274d5d

Browse files
samyronbyroot
authored andcommitted
Allow for segmented output streams and a SWAR-based basic StringEncoder implementation.
1 parent d3f7f04 commit 5274d5d

File tree

7 files changed

+338
-8
lines changed

7 files changed

+338
-8
lines changed

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
116116
file JRUBY_GENERATOR_JAR => :compile do
117117
cd 'java/src' do
118118
generator_classes = FileList[
119-
"json/ext/ByteList*.class",
119+
"json/ext/*ByteList*.class",
120120
"json/ext/OptionsReader*.class",
121121
"json/ext/Generator*.class",
122122
"json/ext/RuntimeInfo*.class",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package json.ext;
2+
3+
import java.io.OutputStream;
4+
5+
import org.jcodings.Encoding;
6+
import org.jruby.util.ByteList;
7+
8+
abstract class AbstractByteListDirectOutputStream extends OutputStream {
9+
10+
private static final String PROP_SEGMENTED_BUFFER = "json.useSegmentedOutputStream";
11+
private static final String PROP_SEGMENTED_BUFFER_DEFAULT = "true";
12+
13+
private static final boolean USE_SEGMENTED_BUFFER;
14+
15+
static {
16+
String useSegmentedOutputStream = System.getProperty(PROP_SEGMENTED_BUFFER, PROP_SEGMENTED_BUFFER_DEFAULT);
17+
USE_SEGMENTED_BUFFER = Boolean.parseBoolean(useSegmentedOutputStream);
18+
// XXX Is there a logger we can use here?
19+
// System.out.println("Using segmented output stream: " + USE_SEGMENTED_BUFFER);
20+
}
21+
22+
public static AbstractByteListDirectOutputStream create(int estimatedSize) {
23+
if (USE_SEGMENTED_BUFFER) {
24+
return new SegmentedByteListDirectOutputStream(estimatedSize);
25+
} else {
26+
return new ByteListDirectOutputStream(estimatedSize);
27+
}
28+
}
29+
30+
public abstract ByteList toByteListDirect(Encoding encoding);
31+
}

java/src/json/ext/ByteListDirectOutputStream.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
import org.jruby.util.ByteList;
55

66
import java.io.IOException;
7-
import java.io.OutputStream;
87
import java.util.Arrays;
98

10-
public class ByteListDirectOutputStream extends OutputStream {
9+
public class ByteListDirectOutputStream extends AbstractByteListDirectOutputStream {
1110
private byte[] buffer;
1211
private int length;
1312

java/src/json/ext/Generator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.jruby.util.IOOutputStream;
2727
import org.jruby.util.TypeConverter;
2828

29+
import json.ext.ByteListDirectOutputStream;
30+
2931
import java.io.BufferedOutputStream;
3032
import java.io.IOException;
3133
import java.io.OutputStream;
@@ -252,7 +254,7 @@ int guessSize(ThreadContext context, Session session, T object) {
252254
}
253255

254256
RubyString generateNew(ThreadContext context, Session session, T object) {
255-
ByteListDirectOutputStream buffer = new ByteListDirectOutputStream(guessSize(context, session, object));
257+
AbstractByteListDirectOutputStream buffer = AbstractByteListDirectOutputStream.create(guessSize(context, session, object));
256258
generateToBuffer(context, session, object, buffer);
257259
return RubyString.newString(context.runtime, buffer.toByteListDirect(UTF8Encoding.INSTANCE));
258260
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package json.ext;
2+
3+
import org.jcodings.Encoding;
4+
import org.jruby.util.ByteList;
5+
6+
import java.io.IOException;
7+
8+
public class LinkedSegmentedByteListDirectOutputStream extends AbstractByteListDirectOutputStream {
9+
private Segment head;
10+
private int length;
11+
private Segment current;
12+
private int numSegments;
13+
14+
private static class Segment {
15+
static final int DEFAULT_SEGMENT_SIZE = 1024;
16+
byte[] buffer;
17+
int length;
18+
Segment next;
19+
20+
Segment() {
21+
this(DEFAULT_SEGMENT_SIZE);
22+
}
23+
24+
Segment(int size) {
25+
if (size <= 0) {
26+
size = DEFAULT_SEGMENT_SIZE;
27+
}
28+
buffer = new byte[Math.max(size, DEFAULT_SEGMENT_SIZE)];
29+
}
30+
}
31+
32+
LinkedSegmentedByteListDirectOutputStream() {
33+
this(Segment.DEFAULT_SEGMENT_SIZE);
34+
}
35+
36+
LinkedSegmentedByteListDirectOutputStream(int size) {
37+
current = head = new Segment(size);
38+
}
39+
40+
public ByteList toByteListDirect(Encoding encoding) {
41+
byte[] buffer = new byte[length];
42+
Segment segment = head;
43+
int pos = 0;
44+
while (segment != null) {
45+
System.arraycopy(segment.buffer, 0, buffer, pos, segment.length);
46+
pos += segment.length;
47+
segment = segment.next;
48+
}
49+
return new ByteList(buffer, 0, length, encoding, false);
50+
}
51+
52+
@Override
53+
public void write(int b) throws IOException {
54+
Segment c = current;
55+
if (c.length == c.buffer.length) {
56+
// This check is deliberately in the case the current segment is full. We want to
57+
// avoid this check in the common case where we have space in the current segment.
58+
if (this.length + 1 < 0) {
59+
throw new IOException("Total length exceeds maximum length of an array.");
60+
}
61+
if (c.next == null) {
62+
numSegments++;
63+
c.next = new Segment(c.buffer.length * 2);
64+
}
65+
c = c.next;
66+
current = c;
67+
}
68+
c.buffer[c.length++] = (byte)b;
69+
length++;
70+
}
71+
72+
@Override
73+
public void write(byte[] bytes, int start, int length) throws IOException {
74+
Segment c = current;
75+
int remaining = length;
76+
77+
while (remaining > 0) {
78+
if (c.length == c.buffer.length) {
79+
// This check is deliberately in the case the current segment is full. We want to
80+
// avoid this check in the common case where we have space in the current segment.
81+
if (this.length + remaining < 0) {
82+
throw new IOException("Total length exceeds maximum length of an array.");
83+
}
84+
if (c.next == null) {
85+
numSegments++;
86+
c.next = new Segment(c.buffer.length * 2);
87+
}
88+
c = c.next;
89+
current = c;
90+
}
91+
int currentLength = c.length;
92+
int currentCapacity = c.buffer.length;
93+
int copyLength = Math.min(remaining, currentCapacity - currentLength);
94+
System.arraycopy(bytes, start, c.buffer, currentLength, copyLength);
95+
c.length += copyLength;
96+
this.length += copyLength;
97+
start += copyLength;
98+
remaining -= copyLength;
99+
}
100+
}
101+
102+
@Override
103+
public void write(byte[] bytes) throws IOException {
104+
write(bytes, 0, bytes.length);
105+
}
106+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package json.ext;
2+
3+
import org.jcodings.Encoding;
4+
import org.jruby.util.ByteList;
5+
6+
import java.io.IOException;
7+
8+
public class SegmentedByteListDirectOutputStream extends AbstractByteListDirectOutputStream {
9+
private static final int DEFAULT_CAPACITY = 1024;
10+
11+
private int totalLength;
12+
private byte[][] segments = new byte[21][];
13+
private int currentSegmentIndex;
14+
private int currentSegmentLength;
15+
private byte[] currentSegment;
16+
17+
SegmentedByteListDirectOutputStream(int size) {
18+
currentSegment = new byte[Math.max(size, DEFAULT_CAPACITY)];
19+
segments[0] = currentSegment;
20+
}
21+
22+
public ByteList toByteListDirect(Encoding encoding) {
23+
byte[] buffer = new byte[totalLength];
24+
int pos = 0;
25+
// We handle the current segment separately.
26+
for (int i = 0; i < currentSegmentIndex; i++) {
27+
byte[] segment = segments[i];
28+
System.arraycopy(segment, 0, buffer, pos, segment.length);
29+
pos += segment.length;
30+
}
31+
System.arraycopy(currentSegment, 0, buffer, pos, currentSegmentLength);
32+
return new ByteList(buffer, 0, totalLength, encoding, false);
33+
}
34+
35+
@Override
36+
public void write(int b) throws IOException {
37+
if (currentSegmentLength == currentSegment.length) {
38+
if (totalLength + 1 < 0) {
39+
throw new IOException("Total length exceeds maximum length of an array.");
40+
}
41+
currentSegmentIndex++;
42+
int capacity = currentSegment.length * 2;
43+
currentSegment = new byte[capacity];
44+
currentSegmentLength = 0;
45+
segments[currentSegmentIndex] = currentSegment;
46+
}
47+
currentSegment[currentSegmentLength++] = (byte) b;
48+
totalLength++;
49+
}
50+
51+
@Override
52+
public void write(byte[] bytes, int start, int length) throws IOException {
53+
int remaining = length;
54+
55+
while (remaining > 0) {
56+
if (currentSegmentLength == currentSegment.length) {
57+
if (totalLength + remaining < 0) {
58+
throw new IOException("Total length exceeds maximum length of an array.");
59+
}
60+
currentSegmentIndex++;
61+
int capacity = currentSegment.length * 2;
62+
currentSegment = new byte[capacity];
63+
currentSegmentLength = 0;
64+
segments[currentSegmentIndex] = currentSegment;
65+
}
66+
int toWrite = Math.min(remaining, currentSegment.length - currentSegmentLength);
67+
System.arraycopy(bytes, start, currentSegment, currentSegmentLength, toWrite);
68+
currentSegmentLength += toWrite;
69+
start += toWrite;
70+
remaining -= toWrite;
71+
}
72+
totalLength += length;
73+
}
74+
75+
@Override
76+
public void write(byte[] bytes) throws IOException {
77+
write(bytes, 0, bytes.length);
78+
}
79+
}

0 commit comments

Comments
 (0)