Skip to content

Commit b8f89b9

Browse files
committed
Introduce a cache for immutable literal values
Immutable literal values like numerics and ranges can be created once globally and never constructed again. Without a global cache, however, both the IR and the JIT will potentially construct them more than once, even if those values may be locally cached. In addition, the IR optimizer will sometimes propagate immutable literal values through variables and copies making it appear that there are multiple of the same literal in a piece of code. This all combines to potentially create more instances of immutable literal values than the single global value actually needed. This PR introduces a global cache for several immutable literal values using an object key and a weak-valued map. So long as one piece of code remains loaded and referencing a given value, it should not be recreated in duplicate operands or call sites. This cache acts as a sort of constant pool for these values.
1 parent 1b8640e commit b8f89b9

8 files changed

Lines changed: 76 additions & 67 deletions

File tree

‎core/src/main/java/org/jruby/Ruby.java‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
import org.jruby.util.WeakIdentityHashMap;
8080
import org.jruby.util.collections.ConcurrentWeakHashMap;
8181
import org.jruby.util.collections.IntHashMap;
82+
import org.jruby.util.collections.WeakValuedMap;
8283
import org.jruby.util.io.EncodingUtils;
8384
import org.objectweb.asm.util.TraceClassVisitor;
8485

@@ -176,6 +177,7 @@
176177
import java.lang.invoke.MethodHandle;
177178
import java.lang.invoke.VarHandle;
178179
import java.lang.ref.WeakReference;
180+
import java.math.BigInteger;
179181
import java.net.BindException;
180182
import java.nio.ByteBuffer;
181183
import java.nio.channels.Channels;
@@ -205,6 +207,7 @@
205207
import java.util.concurrent.atomic.AtomicLong;
206208
import java.util.concurrent.locks.ReentrantLock;
207209
import java.util.function.Consumer;
210+
import java.util.function.Function;
208211
import java.util.function.ToIntFunction;
209212
import java.util.regex.Pattern;
210213

@@ -4756,6 +4759,22 @@ public RuntimeCache getRuntimeCache() {
47564759
return runtimeCache;
47574760
}
47584761

4762+
public IRubyObject cacheFixnum(Long value) {
4763+
return cacheImmutableLiteral(value, (v) -> newFixnum(v));
4764+
}
4765+
4766+
public IRubyObject cacheFloat(Double value) {
4767+
return cacheImmutableLiteral(value, (v) -> newFloat(v));
4768+
}
4769+
4770+
public IRubyObject cacheBignum(BigInteger value) {
4771+
return cacheImmutableLiteral(value, (v) -> RubyBignum.newBignum(this, v));
4772+
}
4773+
4774+
public <T> IRubyObject cacheImmutableLiteral(T key, Function<T, IRubyObject> constructor) {
4775+
return literalCache.computeIfAbsent(key, (k) -> constructor.apply((T) k));
4776+
}
4777+
47594778
public List<StrptimeToken> getCachedStrptimePattern(String pattern) {
47604779
List<StrptimeToken> tokens = strptimeFormatCache.get(pattern);
47614780

@@ -5524,6 +5543,9 @@ public interface RecursiveFunctionEx<T> extends ThreadContext.RecursiveFunctionE
55245543
// A global cache for Java-to-Ruby calls
55255544
private final RuntimeCache runtimeCache;
55265545

5546+
// A global cache for literal values represented as objects
5547+
private final Map<Object, IRubyObject> literalCache = Collections.synchronizedMap(new WeakValuedMap<>());
5548+
55275549
// Message for Errno exceptions that will not generate a backtrace
55285550
public static final String ERRNO_BACKTRACE_MESSAGE = "errno backtraces disabled; run with -Xerrno.backtrace=true to enable";
55295551

‎core/src/main/java/org/jruby/ir/targets/indy/BignumObjectSite.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.jruby.ir.targets.indy;
22

3-
import org.jruby.RubyBignum;
43
import org.jruby.runtime.ThreadContext;
54
import org.jruby.runtime.builtin.IRubyObject;
65
import org.objectweb.asm.Handle;
@@ -38,6 +37,6 @@ public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, Metho
3837
}
3938

4039
public IRubyObject construct(ThreadContext context) {
41-
return RubyBignum.newBignum(context.runtime, value);
40+
return context.runtime.cacheBignum(value);
4241
}
4342
}

‎core/src/main/java/org/jruby/ir/targets/indy/FixnumObjectSite.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
* Created by headius on 10/23/14.
1818
*/
1919
public class FixnumObjectSite extends LazyObjectSite {
20-
private final long value;
20+
// Long to avoid constructing new wrappers for cache call below
21+
private final Long value;
2122

2223
public FixnumObjectSite(MethodType type, long value) {
2324
super(type);
@@ -36,6 +37,6 @@ public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, Metho
3637
}
3738

3839
public IRubyObject construct(ThreadContext context) {
39-
return asFixnum(context, value);
40+
return context.runtime.cacheFixnum(value);
4041
}
4142
}

‎core/src/main/java/org/jruby/ir/targets/indy/FloatObjectSite.java‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.jruby.ir.targets.indy;
22

3-
import org.jruby.RubyFloat;
43
import org.jruby.runtime.ThreadContext;
54
import org.jruby.runtime.builtin.IRubyObject;
65
import org.objectweb.asm.Handle;
@@ -17,7 +16,8 @@
1716
* Created by headius on 10/23/14.
1817
*/
1918
public class FloatObjectSite extends LazyObjectSite {
20-
private final double value;
19+
// Double to avoid constructing new wrappers for cache call below
20+
private final Double value;
2121

2222
public FloatObjectSite(MethodType type, double value) {
2323
super(type);
@@ -37,6 +37,6 @@ public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, Metho
3737
}
3838

3939
public IRubyObject construct(ThreadContext context) {
40-
return RubyFloat.newFloat(context.runtime, value);
40+
return context.runtime.cacheFloat(value);
4141
}
4242
}

‎core/src/main/java/org/jruby/ir/targets/indy/RangeObjectSite.java‎

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public RangeObjectSite(MethodType type, boolean exclusive) {
3434
false);
3535

3636
public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type, int exclusive) {
37-
return new RangeObjectSite(type, exclusive == 1 ? true : false).bootstrap(lookup);
37+
return new RangeObjectSite(type, exclusive == 1).bootstrap(lookup);
3838
}
3939

4040
public IRubyObject construct(ThreadContext context, IRubyObject begin, IRubyObject end) throws Throwable {
@@ -64,28 +64,26 @@ public static CallSite bootstrapFixnums(MethodHandles.Lookup lookup, String name
6464
}
6565

6666
public static class FixnumRangeObjectSite extends RangeObjectSite {
67-
protected final long beginOrOnly;
68-
protected final long end;
69-
protected final boolean beginless;
70-
protected final boolean endless;
67+
protected final FixnumRange range;
7168

7269
public FixnumRangeObjectSite(MethodType type, long beginOrOnly, long end, boolean beginless, boolean endless, boolean exclusive) {
7370
super(type, exclusive);
7471

75-
this.beginOrOnly = beginOrOnly;
76-
this.end = end;
77-
this.beginless = beginless;
78-
this.endless = endless;
72+
this.range = new FixnumRange(beginOrOnly, end, beginless, endless, exclusive);
7973
}
8074

8175
public IRubyObject construct(ThreadContext context) throws Throwable {
82-
if (beginless) {
83-
return RubyRange.newBeginlessRange(context, beginOrOnly, exclusive);
84-
} else if (endless) {
85-
return RubyRange.newEndlessRange(context, beginOrOnly, exclusive);
86-
}
87-
return RubyRange.newRange(context, beginOrOnly, end, exclusive);
76+
return context.runtime.cacheImmutableLiteral(range, (r) -> {
77+
if (r.beginless) {
78+
return RubyRange.newBeginlessRange(context, r.beginOrOnly, r.exclusive);
79+
} else if (r.endless) {
80+
return RubyRange.newEndlessRange(context, r.beginOrOnly, r.exclusive);
81+
}
82+
return RubyRange.newRange(context, r.beginOrOnly, r.end, r.exclusive);
83+
});
8884
}
85+
86+
record FixnumRange(long beginOrOnly, long end, boolean beginless, boolean endless, boolean exclusive) {}
8987
}
9088

9189
public static final Handle BOOTSTRAP_STRING_STRING = new Handle(
@@ -100,26 +98,22 @@ public static CallSite bootstrapStrings(MethodHandles.Lookup lookup, String name
10098
}
10199

102100
public static class StringRangeObjectSite extends RangeObjectSite {
103-
protected final ByteList begin;
104-
protected final int beginCR;
105-
protected final ByteList end;
106-
protected final int endCR;
101+
protected final StringRange range;
107102

108103
public StringRangeObjectSite(MethodType type, ByteList begin, int beginCR, ByteList end, int endCR, boolean exclusive) {
109104
super(type, exclusive);
110105

111-
this.begin = begin;
112-
this.beginCR = beginCR;
113-
this.end = end;
114-
this.endCR = endCR;
106+
this.range = new StringRange(begin, beginCR, end, endCR, exclusive);
115107
}
116108

117109
public IRubyObject construct(ThreadContext context) throws Throwable {
118-
return RubyRange.newRange(
110+
return context.runtime.cacheImmutableLiteral(range, (r) -> RubyRange.newRange(
119111
context,
120-
IRRuntimeHelpers.newFrozenString(context, begin, beginCR),
121-
IRRuntimeHelpers.newFrozenString(context, end, endCR),
122-
exclusive);
112+
IRRuntimeHelpers.newFrozenString(context, r.begin, r.beginCR),
113+
IRRuntimeHelpers.newFrozenString(context, r.end, r.endCR),
114+
r.exclusive));
123115
}
116+
117+
record StringRange(ByteList begin, int beginCR, ByteList end, int endCR, boolean exclusive) {}
124118
}
125119
}

‎core/src/main/java/org/jruby/ir/targets/indy/RegexpObjectSite.java‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.jruby.ir.targets.indy;
22

3-
import org.jruby.RubyRegexp;
43
import org.jruby.ir.runtime.IRRuntimeHelpers;
54
import org.jruby.runtime.ThreadContext;
65
import org.jruby.runtime.builtin.IRubyObject;
@@ -20,14 +19,12 @@
2019
* Created by headius on 10/23/14.
2120
*/
2221
public class RegexpObjectSite extends LazyObjectSite {
23-
protected final ByteList pattern;
24-
protected final RegexpOptions options;
22+
protected final Regexp regexp;
2523

26-
public RegexpObjectSite(MethodType type, ByteList pattern, int embeddedOptions) {
24+
public RegexpObjectSite(MethodType type, ByteList pattern, RegexpOptions options) {
2725
super(type);
2826

29-
this.pattern = pattern;
30-
this.options = RegexpOptions.fromEmbeddedOptions(embeddedOptions);
27+
this.regexp = new Regexp(pattern, options);
3128
}
3229

3330
public static final Handle BOOTSTRAP = new Handle(
@@ -38,13 +35,14 @@ public RegexpObjectSite(MethodType type, ByteList pattern, int embeddedOptions)
3835
false);
3936

4037
public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type, String value, String encodingName, int options) {
41-
return new RegexpObjectSite(type, StringBootstrap.bytelist(value, encodingName), options).bootstrap(lookup);
38+
return new RegexpObjectSite(type, StringBootstrap.bytelist(value, encodingName), RegexpOptions.fromEmbeddedOptions(options)).bootstrap(lookup);
4239
}
4340

4441
// normal regexp
4542
public IRubyObject construct(ThreadContext context) {
46-
RubyRegexp regexp = IRRuntimeHelpers.newLiteralRegexp(context, pattern, options);
47-
48-
return regexp;
43+
return context.runtime.cacheImmutableLiteral(regexp,
44+
(r) -> IRRuntimeHelpers.newLiteralRegexp(context, r.pattern, r.options));
4945
}
46+
47+
record Regexp(ByteList pattern, RegexpOptions options) {}
5048
}

‎core/src/main/java/org/jruby/ir/targets/indy/SymbolObjectSite.java‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.jruby.ir.targets.indy;
22

3-
import org.jruby.RubyEncoding;
4-
import org.jruby.ir.runtime.IRRuntimeHelpers;
53
import org.jruby.runtime.ThreadContext;
64
import org.jruby.runtime.builtin.IRubyObject;
75
import org.jruby.util.ByteList;
@@ -20,14 +18,12 @@
2018
* Created by headius on 10/23/14.
2119
*/
2220
public class SymbolObjectSite extends LazyObjectSite {
23-
private final String value;
24-
private final String encoding;
21+
protected final Symbol symbol;
2522

26-
public SymbolObjectSite(MethodType type, String value, String encoding) {
23+
public SymbolObjectSite(MethodType type, ByteList bytes) {
2724
super(type);
2825

29-
this.value = value;
30-
this.encoding = encoding;
26+
this.symbol = new Symbol(bytes);
3127
}
3228

3329
public static final Handle BOOTSTRAP = new Handle(
@@ -38,11 +34,13 @@ public SymbolObjectSite(MethodType type, String value, String encoding) {
3834
false);
3935

4036
public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type, String value, String encoding) {
41-
return new SymbolObjectSite(type, value, encoding).bootstrap(lookup);
37+
return new SymbolObjectSite(type, StringBootstrap.bytelist(value, encoding)).bootstrap(lookup);
4238
}
4339

4440
public IRubyObject construct(ThreadContext context) {
45-
return asSymbol(context,
46-
new ByteList(RubyEncoding.encodeISO(value), IRRuntimeHelpers.retrieveJCodingsEncoding(context, encoding), false));
41+
return context.runtime.cacheImmutableLiteral(symbol,
42+
(s) -> asSymbol(context, s.bytes));
4743
}
44+
45+
protected record Symbol(ByteList bytes) {}
4846
}
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.jruby.ir.targets.indy;
22

3-
import org.jruby.RubyEncoding;
43
import org.jruby.ir.runtime.IRRuntimeHelpers;
54
import org.jruby.runtime.ThreadContext;
65
import org.jruby.runtime.builtin.IRubyObject;
@@ -16,15 +15,13 @@
1615
import static org.jruby.util.CodegenUtils.p;
1716
import static org.jruby.util.CodegenUtils.sig;
1817

19-
public class SymbolProcObjectSite extends LazyObjectSite {
20-
private final String value;
21-
private final String encoding;
18+
public class SymbolProcObjectSite extends SymbolObjectSite {
19+
private final SymbolProc symbolProc;
2220

23-
public SymbolProcObjectSite(MethodType type, String value, String encoding) {
24-
super(type);
21+
public SymbolProcObjectSite(MethodType type, ByteList bytes) {
22+
super(type, bytes);
2523

26-
this.value = value;
27-
this.encoding = encoding;
24+
symbolProc = new SymbolProc(symbol);
2825
}
2926

3027
public static final Handle BOOTSTRAP = new Handle(
@@ -35,13 +32,13 @@ public SymbolProcObjectSite(MethodType type, String value, String encoding) {
3532
false);
3633

3734
public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type, String value, String encoding) {
38-
return new SymbolProcObjectSite(type, value, encoding).bootstrap(lookup);
35+
return new SymbolProcObjectSite(type, StringBootstrap.bytelist(value, encoding)).bootstrap(lookup);
3936
}
4037

4138
public IRubyObject construct(ThreadContext context) {
42-
var symbol = asSymbol(context, new ByteList(RubyEncoding.encodeISO(value),
43-
IRRuntimeHelpers.retrieveJCodingsEncoding(context, encoding), false));
44-
45-
return IRRuntimeHelpers.newSymbolProc(context, symbol);
39+
return context.runtime.cacheImmutableLiteral(symbolProc,
40+
(sp) -> IRRuntimeHelpers.newSymbolProc(context, asSymbol(context, sp.symbol.bytes())));
4641
}
42+
43+
record SymbolProc(Symbol symbol) {}
4744
}

0 commit comments

Comments
 (0)