Skip to content

Commit

Permalink
More string interpolation optimizations
Browse files Browse the repository at this point in the history
* Don't re-copy the buffer when creating initial buffer from first
  static string.
* Log how the dstring has been bound by indy
* Additional cases in benchmark
* Benchmark reuses proc to omit block creation overhead.
* Stringable returns same frozen string to avoid new string cost.
  • Loading branch information
headius committed May 14, 2024
1 parent bbcad68 commit b4ec0e9
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 5 deletions.
5 changes: 3 additions & 2 deletions bench/bench_block_given.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ def foo2
end

Benchmark.ips do |bm|
pr = proc { }
bm.report("block") do |i|
while i > 0
i-=1
foo { }; foo { }; foo { }; foo { }; foo { }; foo { }; foo { }; foo { }; foo { }; foo { }
foo ≺ foo ≺ foo ≺ foo ≺ foo ≺ foo ≺ foo ≺ foo ≺ foo ≺ foo &pr
end
end

Expand All @@ -26,7 +27,7 @@ def foo2
bm.report("defined block") do |i|
while i > 0
i-=1
foo2 { }; foo2 { }; foo2 { }; foo2 { }; foo2 { }; foo2 { }; foo2 { }; foo2 { }; foo2 { }; foo2 { }
foo2 ≺ foo2 ≺ foo2 ≺ foo2 ≺ foo2 ≺ foo2 ≺ foo2 ≺ foo2 ≺ foo2 ≺ foo2 &pr
end
end

Expand Down
21 changes: 20 additions & 1 deletion bench/bench_string_interpolation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class Stringable
def to_s
"quux"
"quux".freeze
end
end

Expand Down Expand Up @@ -96,6 +96,15 @@ def to_s
a
}

bm.report('"foo#{n}bar#{n}baz#{n}" fixnum') {|n|
a = nil
while n > 0
n-=1
a = "foo#{n}bar#{n}baz"
end
a
}

bm.report('"#{x}#{x}#{x}#{x}#{x}" to_s') {|n|
a = nil
x = Stringable.new
Expand All @@ -105,4 +114,14 @@ def to_s
end
a
}

bm.report('"#{x}#{x}#{x}#{x}#{x}#{x}#{x}#{x}#{x}#{x}" to_s') {|n|
a = nil
x = Stringable.new
while n > 0
n-=1
a = "#{x}#{x}#{x}#{x}#{x}#{x}#{x}#{x}#{x}#{x}"
end
a
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.StringSupport;
import org.jruby.util.cli.Options;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;

Expand All @@ -24,6 +27,8 @@
import static org.jruby.util.CodegenUtils.sig;

public class BuildDynamicStringSite extends MutableCallSite {
private static final Logger LOG = LoggerFactory.getLogger(BuildDynamicStringSite.class);

public static final Handle BUILD_DSTRING_BOOTSTRAP = new Handle(
Opcodes.H_INVOKESTATIC,
p(BuildDynamicStringSite.class),
Expand Down Expand Up @@ -94,13 +99,22 @@ public BuildDynamicStringSite(MethodType type, Object[] stringArgs) {

if (specialize) {
// bind directly to specialized builds
if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) {
LOG.info("dstring(" + Long.toBinaryString(descriptor) +")" + "\tbound directly");
}
binder = binder.append(arrayOf(Encoding.class, int.class), encoding, initialSize);
setTarget(binder.invokeStaticQuiet(BuildDynamicStringSite.class, "buildString"));
} else if (dynamicArgs <= MAX_DYNAMIC_ARGS_FOR_SPECIALIZE2) {
// second level specialization, using call site to hold strings, no argument[] box, and appending in a loop
if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) {
LOG.info("dstring(" + Long.toBinaryString(descriptor) +")" + "\tbound to unrolled loop");
}
binder = binder.prepend(this).append(arrayOf(Encoding.class, int.class), encoding, initialSize);
setTarget(binder.invokeVirtualQuiet("buildString2"));
} else {
if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) {
LOG.info("dstring(" + Long.toBinaryString(descriptor) +")" + "\tbound to loop");
}
binder = binder.prepend(this).collect(2, IRubyObject[].class);
setTarget(binder.invokeVirtualQuiet("buildString"));
}
Expand Down Expand Up @@ -462,9 +476,9 @@ private static RubyString createBufferFromStaticString(ThreadContext context, in
byte[] bufferArray = Arrays.copyOfRange(firstStringByteList.unsafeBytes(), firstStringByteList.begin(), initialSize);

// use element realSize for starting buffer realSize
ByteList bufferByteList = new ByteList(bufferArray, 0, firstStringByteList.realSize());
ByteList bufferByteList = new ByteList(bufferArray, 0, firstStringByteList.realSize(), firstStringByteList.getEncoding(), false);

buffer = RubyString.newStringNoCopy(context.runtime, bufferByteList, firstStringByteList.getEncoding(), firstStringCR);
buffer = RubyString.newString(context.runtime, bufferByteList, firstStringCR);
return buffer;
}

Expand Down

0 comments on commit b4ec0e9

Please sign in to comment.