Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply recent C optimizations to Java extension #725

Draft
wants to merge 24 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
776993a
Make benchmark runnable without oj available
headius Jan 8, 2025
be45c9a
Port convert_UTF8_to_ASCII_only_JSON to Java
headius Jan 9, 2025
4d37e9f
Align string generate method with generate_json_string
headius Jan 9, 2025
38c7831
Port convert_UTF8_to_JSON from C
headius Jan 9, 2025
0a5f6e7
Use external iteration to reduce alloc
headius Jan 9, 2025
98cb785
Remove unused imports
headius Jan 9, 2025
75cf6fe
Inline ConvertBytes logic for long to byte[]
headius Jan 9, 2025
8f4ce51
Eliminate * import
headius Jan 9, 2025
845fc46
Restructure handlers for easier profiling
headius Jan 9, 2025
9d74a1f
Avoid allocation when writing Array delimiters
headius Jan 9, 2025
92b24de
Move away from Handler abstraction
headius Jan 9, 2025
f7eede3
Match C version of fbuffer_append_long
headius Jan 15, 2025
b11e4f2
Minor tweaks to reduce complexity
headius Jan 15, 2025
97ac36f
Reimpl byte[] stream without synchronization
headius Jan 15, 2025
bd2007a
Reduce overhead in repeats
headius Jan 15, 2025
4f7d404
Use equivalent of rb_sym2str
headius Jan 16, 2025
10d752b
Microoptimizations for ByteList stream
headius Jan 16, 2025
39d410f
Cast to byte not necessary
headius Jan 16, 2025
7f9b6a3
Refactor this for better inlining
headius Jan 16, 2025
70aadd0
More tiny tweaks to reduce overhead of generateString
headius Jan 16, 2025
0133cbc
Refactor to avoid repeated boolean checks
headius Jan 16, 2025
9de3120
Eliminate memory accesses for digits
headius Jan 16, 2025
d0a718c
Loosen visibility to avoid accessor methods
headius Jan 16, 2025
67a00da
Modify parser bench to work without oj or rapidjson
headius Jan 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions benchmark/encoder.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
require "benchmark/ips"
require "json"
require "date"
require "oj"

Oj.default_options = Oj.default_options.merge(mode: :compat)
begin
require "oj"

Oj.default_options = Oj.default_options.merge(mode: :compat)
rescue LoadError
# no oj, just do json
end

if ENV["ONLY"]
RUN = ENV["ONLY"].split(/[,: ]/).map{|x| [x.to_sym, true] }.to_h
Expand All @@ -16,11 +21,11 @@
end

def implementations(ruby_obj)
state = JSON::State.new(JSON.dump_default_options)
{
json: ["json", proc { JSON.generate(ruby_obj) }],
oj: ["oj", proc { Oj.dump(ruby_obj) }],
}
impls = { json: ["json", proc { JSON.generate(ruby_obj) }] }
if defined? Oj
impls["oj"] = proc { Oj.dump(ruby_obj) }
end
impls
end

def benchmark_encoding(benchmark_name, ruby_obj, check_expected: true, except: [])
Expand Down
20 changes: 15 additions & 5 deletions benchmark/parser.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
require "benchmark/ips"
require "json"
require "oj"
require "rapidjson"
begin
require "oj"
rescue LoadError
end
begin
require "rapidjson"
rescue LoadError
end

if ENV["ONLY"]
RUN = ENV["ONLY"].split(/[,: ]/).map{|x| [x.to_sym, true] }.to_h
Expand All @@ -18,9 +24,13 @@ def benchmark_parsing(name, json_output)

Benchmark.ips do |x|
x.report("json") { JSON.parse(json_output) } if RUN[:json]
x.report("oj") { Oj.load(json_output) } if RUN[:oj]
x.report("Oj::Parser") { Oj::Parser.new(:usual).parse(json_output) } if RUN[:oj]
x.report("rapidjson") { RapidJSON.parse(json_output) } if RUN[:rapidjson]
if defined?(Oj)
x.report("oj") { Oj.load(json_output) } if RUN[:oj]
x.report("Oj::Parser") { Oj::Parser.new(:usual).parse(json_output) } if RUN[:oj]
end
if defined?(RapidJSON)
x.report("rapidjson") { RapidJSON.parse(json_output) } if RUN[:rapidjson]
end
x.compare!(order: :baseline)
end
puts
Expand Down
66 changes: 62 additions & 4 deletions java/src/json/ext/ByteListDirectOutputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,72 @@
import org.jcodings.Encoding;
import org.jruby.util.ByteList;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;

public class ByteListDirectOutputStream extends OutputStream {
private byte[] buffer;
private int length;

public class ByteListDirectOutputStream extends ByteArrayOutputStream {
ByteListDirectOutputStream(int size) {
super(size);
buffer = new byte[size];
}

public ByteList toByteListDirect(Encoding encoding) {
return new ByteList(buf, 0, count, encoding, false);
return new ByteList(buffer, 0, length, encoding, false);
}

@Override
public void write(int b) throws IOException {
int currentLength = this.length;
int newLength = currentLength + 1;
byte[] buffer = ensureBuffer(this, newLength);
buffer[currentLength] = (byte) b;
this.length = newLength;
}

@Override
public void write(byte[] bytes, int start, int length) throws IOException {
int currentLength = this.length;
int newLength = currentLength + length;
byte[] buffer = ensureBuffer(this, newLength);
System.arraycopy(bytes, start, buffer, currentLength, length);
this.length = newLength;
}

@Override
public void write(byte[] bytes) throws IOException {
int myLength = this.length;
int moreLength = bytes.length;
int newLength = myLength + moreLength;
byte[] buffer = ensureBuffer(this, newLength);
System.arraycopy(bytes, 0, buffer, myLength, moreLength);
this.length = newLength;
}

private static byte[] ensureBuffer(ByteListDirectOutputStream self, int minimumLength) {
byte[] buffer = self.buffer;
int myCapacity = buffer.length;
int diff = minimumLength - myCapacity;
if (diff > 0) {
buffer = self.buffer = grow(buffer, myCapacity, diff);
}

return buffer;
}

private static byte[] grow(byte[] oldBuffer, int myCapacity, int diff) {
// grow to double current buffer length or capacity + diff, whichever is greater
int newLength = myCapacity + Math.max(myCapacity, diff);
// check overflow
if (newLength < 0) {
// try just diff length in case it can fit
newLength = myCapacity + diff;
if (newLength < 0) {
throw new ArrayIndexOutOfBoundsException("cannot allocate array of size " + myCapacity + "+" + diff);
}
}
return Arrays.copyOf(oldBuffer, newLength);
}
}
4 changes: 3 additions & 1 deletion java/src/json/ext/ByteListTranscoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,11 @@ protected void quoteStart() {
* until the character before it.
*/
protected void quoteStop(int endPos) throws IOException {
int quoteStart = this.quoteStart;
if (quoteStart != -1) {
ByteList src = this.src;
append(src.unsafeBytes(), src.begin() + quoteStart, endPos - quoteStart);
quoteStart = -1;
this.quoteStart = -1;
}
}

Expand Down
Loading
Loading