From 4d4f7f5a99fc433bb6389fa1eb3ad3487d6622fd Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 27 Mar 2024 23:48:43 -0500 Subject: [PATCH] Optimize retrieval of method/callee name from compound Prior to this patch the compound name passed on the stack from an AliasMethod was repeatedly parsed and split before acquiring the associated symbol, leading to wasteful allocation of additional String objects. The change here adds a new map to the symbol table that tracks the two symbols associated with a compound name, so that only a single lookup is needed and no allocation happens. --- core/src/main/java/org/jruby/RubySymbol.java | 27 +++++++++++++++++++ .../ir/instructions/FrameNameCallInstr.java | 9 +++---- .../jruby/ir/targets/indy/FrameNameSite.java | 5 ++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/jruby/RubySymbol.java b/core/src/main/java/org/jruby/RubySymbol.java index 506b782b24f..db00decf9d5 100644 --- a/core/src/main/java/org/jruby/RubySymbol.java +++ b/core/src/main/java/org/jruby/RubySymbol.java @@ -71,6 +71,9 @@ import org.jruby.util.TypeConverter; import java.lang.ref.WeakReference; +import java.util.AbstractMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import static org.jruby.util.RubyStringBuilder.str; @@ -346,6 +349,14 @@ public static RubySymbol newHardSymbol(Ruby runtime, String name) { return runtime.getSymbolTable().getSymbol(name, true); } + public static RubySymbol newMethodSymbolFromCompound(Ruby runtime, String compoundName) { + return runtime.getSymbolTable().getCompoundSymbol(compoundName).getKey(); + } + + public static RubySymbol newCalleeSymbolFromCompound(Ruby runtime, String compoundName) { + return runtime.getSymbolTable().getCompoundSymbol(compoundName).getValue(); + } + /** * Return the symbol in the symbol table if it exists, null otherwise. * This method will not create the symbol if it does not exist. @@ -979,6 +990,7 @@ public static final class SymbolTable { private final ReentrantLock tableLock = new ReentrantLock(); private volatile SymbolEntry[] symbolTable; + private Map> compoundSymbolTable = new ConcurrentHashMap<>(); private int size; private int threshold; private final float loadFactor; @@ -1093,6 +1105,21 @@ public RubySymbol getSymbol(ByteList bytes, ObjBooleanConsumer handl return symbol; } + /** + * Get a pair of symbols associated with the given compound method name, used by aliases to pass both the callee + * name and the original method name on the stack. This avoids re-parsing the compoundName and constructing new + * strings every time __method__ or __callee__ are used in an aliased call. + * + * @param compoundName the compound name used for a combination of alias and method + * @return a Map.Entry representing the __method__ and __callee__ symbols for that compound name as key and value + */ + public Map.Entry getCompoundSymbol(String compoundName) { + return compoundSymbolTable.computeIfAbsent(compoundName, (cname) -> + new AbstractMap.SimpleImmutableEntry( + getSymbol(Helpers.getSuperNameFromCompositeName(cname), true), + getSymbol(Helpers.getCalleeNameFromCompositeName(cname), true))); + } + private RubySymbol findSymbol(ByteList bytes, int hash, boolean hard) { RubySymbol symbol = null; diff --git a/core/src/main/java/org/jruby/ir/instructions/FrameNameCallInstr.java b/core/src/main/java/org/jruby/ir/instructions/FrameNameCallInstr.java index c88c697a78d..649382356a9 100644 --- a/core/src/main/java/org/jruby/ir/instructions/FrameNameCallInstr.java +++ b/core/src/main/java/org/jruby/ir/instructions/FrameNameCallInstr.java @@ -1,12 +1,12 @@ package org.jruby.ir.instructions; +import org.jruby.RubySymbol; import org.jruby.ir.IRVisitor; import org.jruby.ir.Operation; import org.jruby.ir.operands.Variable; import org.jruby.ir.persistence.IRReaderDecoder; import org.jruby.ir.persistence.IRWriterEncoder; import org.jruby.ir.transformations.inlining.CloneInfo; -import org.jruby.runtime.Helpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.callsite.CacheEntry; @@ -56,10 +56,9 @@ public IRubyObject getFrameName(ThreadContext context, IRubyObject self, String return frameNameSite.call(context, self, self); } - return context.runtime.newSymbol( - callee ? - Helpers.getSuperNameFromCompositeName(compositeName) : - Helpers.getCalleeNameFromCompositeName(compositeName)); + return callee ? + RubySymbol.newCalleeSymbolFromCompound(context.runtime, compositeName): + RubySymbol.newMethodSymbolFromCompound(context.runtime, compositeName); } @Override diff --git a/core/src/main/java/org/jruby/ir/targets/indy/FrameNameSite.java b/core/src/main/java/org/jruby/ir/targets/indy/FrameNameSite.java index 736dcf4561a..bc17bcd02e0 100644 --- a/core/src/main/java/org/jruby/ir/targets/indy/FrameNameSite.java +++ b/core/src/main/java/org/jruby/ir/targets/indy/FrameNameSite.java @@ -1,6 +1,7 @@ package org.jruby.ir.targets.indy; import com.headius.invokebinder.Binder; +import org.jruby.RubySymbol; import org.jruby.runtime.Helpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -69,10 +70,10 @@ public IRubyObject frameNameFallback(String name, ThreadContext context, IRubyOb } public static IRubyObject __callee__(ThreadContext context, String frameName) { - return context.runtime.newSymbol(Helpers.getCalleeNameFromCompositeName(frameName)); + return RubySymbol.newCalleeSymbolFromCompound(context.runtime, frameName); } public static IRubyObject __method__(ThreadContext context, String frameName) { - return context.runtime.newSymbol(Helpers.getSuperNameFromCompositeName(frameName)); + return RubySymbol.newMethodSymbolFromCompound(context.runtime, frameName); } }