Skip to content

Commit

Permalink
Merge pull request jruby#8260 from headius/fast_marshal
Browse files Browse the repository at this point in the history
Rewrite Marshal.dump logic for efficiency
  • Loading branch information
headius authored May 28, 2024
2 parents 8a212cf + b236433 commit d860015
Show file tree
Hide file tree
Showing 28 changed files with 1,823 additions and 62 deletions.
68 changes: 68 additions & 0 deletions bench/bench_marshal_dump.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
require 'benchmark/ips'

ARRAY_OF_FIXNUMS = [1,2,3,4,5,6,7,8,9,10]
ARRAY_OF_BIG_FIXNUMS = [1<<30,1<<30,1<<30,1<<30,1<<30,1<<30,1<<30,1<<30,1<<30,1<<30]
ARRAY_OF_FLOATS = [1.1111111111,2.2222222222,3.3333333333,4.4444444444,5.5555555555,6.6666666666,7.7777777777,8.8888888888,9.9999999999,10.111111111]
ARRAY_OF_STRINGS = ARRAY_OF_FLOATS.map(&:to_s)
ARRAY_OF_ARRAYS = ARRAY_OF_FIXNUMS.map {[it]}
ARRAY_OF_HASHES = ARRAY_OF_FIXNUMS.map {{it => it}}

class Foo
def initialize(val)
@val = val
end
end

ARRAY_OF_OBJECTS = ARRAY_OF_FIXNUMS.map {Foo.new(it)}
ARRAY_OF_TIMES = 10.times.map {Time.now}

Benchmark.ips do |bm|
bm.report("array of fixnums") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_FIXNUMS)
end
end
bm.report("array of big fixnums") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_BIG_FIXNUMS)
end
end
bm.report("array of floats") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_FLOATS)
end
end
bm.report("array of strings") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_STRINGS)
end
end
bm.report("array of arrays") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_ARRAYS)
end
end
bm.report("array of hashes") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_HASHES)
end
end
bm.report("array of objects") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_OBJECTS)
end
end
bm.report("array of times") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_TIMES)
end
end
end
70 changes: 70 additions & 0 deletions bench/bench_marshal_load.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require 'benchmark/ips'

ARRAY_OF_FIXNUMS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ARRAY_OF_FIXNUMS_DUMPED = Marshal.dump(ARRAY_OF_FIXNUMS)
ARRAY_OF_BIG_FIXNUMS_DUMPED = Marshal.dump([1<<30,1<<30,1<<30,1<<30,1<<30,1<<30,1<<30,1<<30,1<<30,1<<30])
ARRAY_OF_FLOATS = [1.1111111111, 2.2222222222, 3.3333333333, 4.4444444444, 5.5555555555, 6.6666666666, 7.7777777777, 8.8888888888, 9.9999999999, 10.111111111]
ARRAY_OF_FLOATS_DUMPED = Marshal.dump(ARRAY_OF_FLOATS)
ARRAY_OF_STRINGS_DUMPED = Marshal.dump(ARRAY_OF_FLOATS.map(&:to_s))
ARRAY_OF_ARRAYS_DUMPED = Marshal.dump(ARRAY_OF_FIXNUMS.map {[it]})
ARRAY_OF_HASHES_DUMPED = Marshal.dump(ARRAY_OF_FIXNUMS.map {{it => it}})

class Foo
def initialize(val)
@val = val
end
end

ARRAY_OF_OBJECTS_DUMPED = Marshal.dump(ARRAY_OF_FIXNUMS.map {Foo.new(it)})
ARRAY_OF_TIMES_DUMPED = Marshal.dump(10.times.map {Time.now})

Benchmark.ips do |bm|
bm.report("array of fixnums") do |i|
while i > 0
i-=1
Marshal.load(ARRAY_OF_FIXNUMS_DUMPED)
end
end
bm.report("array of big fixnums") do |i|
while i > 0
i-=1
Marshal.load(ARRAY_OF_BIG_FIXNUMS_DUMPED)
end
end
bm.report("array of floats") do |i|
while i > 0
i-=1
Marshal.load(ARRAY_OF_FLOATS_DUMPED)
end
end
bm.report("array of strings") do |i|
while i > 0
i-=1
Marshal.load(ARRAY_OF_STRINGS_DUMPED)
end
end
bm.report("array of arrays") do |i|
while i > 0
i-=1
Marshal.load(ARRAY_OF_ARRAYS_DUMPED)
end
end
bm.report("array of hashes") do |i|
while i > 0
i-=1
Marshal.load(ARRAY_OF_HASHES_DUMPED)
end
end
bm.report("array of objects") do |i|
while i > 0
i-=1
Marshal.load(ARRAY_OF_OBJECTS_DUMPED)
end
end
bm.report("array of times") do |i|
while i > 0
i-=1
Marshal.dump(ARRAY_OF_TIMES_DUMPED)
end
end
end
3 changes: 2 additions & 1 deletion core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ private Ruby(RubyInstanceConfig config) {
encodingService = new EncodingService(this);

symbolClass = RubySymbol.createSymbolClass(this);
symbolTable = new RubySymbol.SymbolTable(this);

threadGroupClass = profile.allowClass("ThreadGroup") ? RubyThreadGroup.createThreadGroupClass(this) : null;
threadClass = profile.allowClass("Thread") ? RubyThread.createThreadClass(this) : null;
Expand Down Expand Up @@ -5349,7 +5350,7 @@ public RubyClass getData() {

private final ObjectSpace objectSpace = new ObjectSpace();

private final RubySymbol.SymbolTable symbolTable = new RubySymbol.SymbolTable(this);
private final RubySymbol.SymbolTable symbolTable;

private boolean abortOnException = false; // Thread.abort_on_exception
private boolean reportOnException = true; // Thread.report_on_exception
Expand Down
17 changes: 16 additions & 1 deletion core/src/main/java/org/jruby/RubyArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.api.Error;
import org.jruby.ast.util.ArgsUtil;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.exceptions.RaiseException;
Expand All @@ -76,6 +75,7 @@
import org.jruby.runtime.callsite.CachingCallSite;
import org.jruby.runtime.encoding.EncodingCapable;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.NewMarshal;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.specialized.RubyArrayOneObject;
import org.jruby.specialized.RubyArraySpecialized;
Expand Down Expand Up @@ -5247,6 +5247,21 @@ public static void marshalTo(RubyArray array, MarshalStream output) throws IOExc
}
}

public static void marshalTo(RubyArray array, NewMarshal output, ThreadContext context, NewMarshal.RubyOutputStream out) {
output.registerLinkTarget(array);

int length = array.realLength;

output.writeInt(out, length);
try {
for (int i = 0; i < length; i++) {
output.dumpObject(context, out, array.eltInternal(i));
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw concurrentModification(array.getRuntime(), ex);
}
}

public static RubyArray unmarshalFrom(UnmarshalStream input) throws IOException {
int size = input.unmarshalInt();

Expand Down
27 changes: 27 additions & 0 deletions core/src/main/java/org/jruby/RubyBignum.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.CachingCallSite;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.NewMarshal;
import org.jruby.runtime.marshal.UnmarshalStream;

import static org.jruby.RubyFixnum.zero;
Expand Down Expand Up @@ -1246,6 +1247,32 @@ public static void marshalTo(RubyBignum bignum, MarshalStream output) throws IOE
}
}

public static void marshalTo(RubyBignum bignum, NewMarshal output, NewMarshal.RubyOutputStream out) {
output.registerLinkTarget(bignum);

out.write(bignum.value.signum() >= 0 ? '+' : '-');

BigInteger absValue = bignum.value.abs();

byte[] digits = absValue.toByteArray();

boolean oddLengthNonzeroStart = (digits.length % 2 != 0 && digits[0] != 0);
int shortLength = digits.length / 2;
if (oddLengthNonzeroStart) {
shortLength++;
}
output.writeInt(out, shortLength);

for (int i = 1; i <= shortLength * 2 && i <= digits.length; i++) {
out.write(digits[digits.length - i]);
}

if (oddLengthNonzeroStart) {
// Pad with a 0
out.write(0);
}
}

public static RubyNumeric unmarshalFrom(UnmarshalStream input) throws IOException {
boolean positive = input.readUnsignedByte() == '+';
int shortLength = input.unmarshalInt();
Expand Down
71 changes: 71 additions & 0 deletions core/src/main/java/org/jruby/RubyClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import org.jruby.runtime.ivars.VariableAccessorField;
import org.jruby.runtime.ivars.VariableTableManager;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.NewMarshal;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.runtime.opto.Invalidator;
import org.jruby.util.ArraySupport;
Expand Down Expand Up @@ -1263,6 +1264,10 @@ public final void marshal(Object obj, MarshalStream marshalStream) throws IOExce
getMarshal().marshalTo(runtime, obj, this, marshalStream);
}

public final void marshal(Object obj, NewMarshal marshalStream, ThreadContext context, NewMarshal.RubyOutputStream out) {
getMarshal().marshalTo(obj, this, marshalStream, context, out);
}

public final Object unmarshal(UnmarshalStream unmarshalStream) throws IOException {
return getMarshal().unmarshalFrom(runtime, this, unmarshalStream);
}
Expand All @@ -1272,6 +1277,11 @@ public static void marshalTo(RubyClass clazz, MarshalStream output) throws java.
output.writeString(MarshalStream.getPathFromClass(clazz));
}

public static void marshalTo(RubyClass clazz, NewMarshal output, NewMarshal.RubyOutputStream out) {
output.registerLinkTarget(clazz);
output.writeString(out, MarshalStream.getPathFromClass(clazz));
}

public static RubyClass unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
String name = RubyString.byteListToString(input.unmarshalString());
return UnmarshalStream.getClassFromPath(input.getRuntime(), name);
Expand All @@ -1286,6 +1296,14 @@ public void marshalTo(Ruby runtime, Object obj, RubyClass type, MarshalStream ma
marshalStream.dumpVariables(object.getMarshalVariableList());
}

@Override
public void marshalTo(Object obj, RubyClass type, NewMarshal marshalStream, ThreadContext context, NewMarshal.RubyOutputStream out) {
IRubyObject object = (IRubyObject) obj;

marshalStream.registerLinkTarget(object);
marshalStream.dumpVariables(context, out, object);
}

@Override
public Object unmarshalFrom(Ruby runtime, RubyClass type, UnmarshalStream input) throws IOException {
IRubyObject result = input.entry(type.allocate());
Expand Down Expand Up @@ -2535,7 +2553,28 @@ public void dump(MarshalStream stream, IRubyObject object) throws IOException {
} else {
stream.writeDirectly(object);
}
}
}

public void dump(NewMarshal stream, ThreadContext context, NewMarshal.RubyOutputStream out, IRubyObject object) {
switch (type) {
case DEFAULT:
stream.writeDirectly(context, out, object);
return;
case NEW_USER:
stream.userNewMarshal(context, out, object, entry);
return;
case OLD_USER:
stream.userMarshal(context, out, object, entry);
return;
case DEFAULT_SLOW:
if (object.respondsTo("marshal_dump")) {
stream.userNewMarshal(context, out, object);
} else if (object.respondsTo("_dump")) {
stream.userMarshal(context, out, object);
} else {
stream.writeDirectly(context, out, object);
}
}
}

Expand Down Expand Up @@ -2601,6 +2640,38 @@ public void smartDump(MarshalStream stream, IRubyObject target) throws IOExcepti
tuple.dump(stream, target);
}

public void smartDump(NewMarshal stream, ThreadContext context, NewMarshal.RubyOutputStream out, IRubyObject target) {
MarshalTuple tuple;
if ((tuple = cachedDumpMarshal).generation == generation) {
} else {
// recache
CacheEntry entry = searchWithCache("respond_to?");
DynamicMethod method = entry.method;
if (!method.equals(runtime.getRespondToMethod()) && !method.isUndefined()) {

// custom respond_to?, always do slow default marshaling
tuple = (cachedDumpMarshal = new MarshalTuple(null, MarshalType.DEFAULT_SLOW, generation));

} else if (!(entry = searchWithCache("marshal_dump")).method.isUndefined()) {

// object really has 'marshal_dump', cache "new" user marshaling
tuple = (cachedDumpMarshal = new MarshalTuple(entry, MarshalType.NEW_USER, generation));

} else if (!(entry = searchWithCache("_dump")).method.isUndefined()) {

// object really has '_dump', cache "old" user marshaling
tuple = (cachedDumpMarshal = new MarshalTuple(entry, MarshalType.OLD_USER, generation));

} else {

// no respond_to?, marshal_dump, or _dump, so cache default marshaling
tuple = (cachedDumpMarshal = new MarshalTuple(null, MarshalType.DEFAULT, generation));
}
}

tuple.dump(stream, context, out, target);
}

/**
* Load marshaled data into a blank target object using marshal_load, being
* "smart" and caching the mechanism for invoking marshal_load.
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/java/org/jruby/RubyComplex.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.NewMarshal;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;
import org.jruby.util.Numeric;
Expand Down Expand Up @@ -1109,6 +1110,11 @@ public void marshalTo(Ruby runtime, Object obj, RubyClass type, MarshalStream ma
//do nothing
}

@Override
public void marshalTo(Object obj, RubyClass type, NewMarshal marshalStream, ThreadContext context, NewMarshal.RubyOutputStream out) {
//do nothing
}

@Override
public Object unmarshalFrom(Ruby runtime, RubyClass type,
UnmarshalStream unmarshalStream) throws IOException {
Expand Down
11 changes: 11 additions & 0 deletions core/src/main/java/org/jruby/RubyException.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.jruby.runtime.builtin.Variable;
import org.jruby.runtime.component.VariableEntry;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.NewMarshal;
import org.jruby.runtime.marshal.UnmarshalStream;

import java.io.IOException;
Expand Down Expand Up @@ -188,6 +189,16 @@ public void marshalTo(Ruby runtime, RubyException exc, RubyClass type,
marshalStream.dumpVariables(attrs);
}

@Override
public void marshalTo(RubyException exc, RubyClass type,
NewMarshal marshalStream, ThreadContext context, NewMarshal.RubyOutputStream out) {
marshalStream.registerLinkTarget(exc);
marshalStream.dumpVariables(context, out, exc, 2, (marshal, c, o, v, receiver) -> {
receiver.receive(marshal, c, o, "mesg", v.getMessage());
receiver.receive(marshal, c, o, "bt", v.getBacktrace());
});
}

@Override
public RubyException unmarshalFrom(Ruby runtime, RubyClass type, UnmarshalStream input) throws IOException {
RubyException exc = (RubyException) input.entry(type.allocate());
Expand Down
Loading

0 comments on commit d860015

Please sign in to comment.