pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/jruby/jruby/commit/8ab3c8fa5e3a3335286ffb8ecde6fcba6bc59aaf

imer-b48faa60c69660fa.css" /> Optimize literal fixnum and float arrays · jruby/jruby@8ab3c8f · GitHub
Skip to content

Commit 8ab3c8f

Browse files
committed
Optimize literal fixnum and float arrays
This change detects when a literal array contains only fixnums or floats and uses indy to embed those values in a call site, greatly reducing the bytecode size for constructing the array. Arrays that are too large will not fit into the encoded String and prevent compilation, but they would not likely have fit into bytecode limits with the old logic anyway. A benchmark is included.
1 parent c65b038 commit 8ab3c8f

11 files changed

Lines changed: 343 additions & 4 deletions

File tree

bench/bench_literal_array.rb

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
require 'benchmark/ips'
2+
3+
def array0
4+
[]
5+
end
6+
7+
def fixnum_array1
8+
[1]
9+
end
10+
11+
def fixnum_array2
12+
[1,2]
13+
end
14+
15+
def fixnum_array3
16+
[1,2,3]
17+
end
18+
19+
def fixnum_array10
20+
[1,2,3,4,5,6,7,8,9,10]
21+
end
22+
23+
def float_array1
24+
[1.0]
25+
end
26+
27+
def float_array2
28+
[1.0,2.0]
29+
end
30+
31+
def float_array3
32+
[1.0,2.0,3.0]
33+
end
34+
35+
def float_array10
36+
[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0]
37+
end
38+
39+
Benchmark.ips do |bm|
40+
bm.report("empty array") do |i|
41+
n = 0
42+
while i > 0
43+
i-=1
44+
n += array0.size
45+
end
46+
n
47+
end
48+
49+
bm.report("fixnum array 1") do |i|
50+
n = 0
51+
while i > 0
52+
i-=1
53+
n += fixnum_array1.size
54+
end
55+
n
56+
end
57+
58+
bm.report("fixnum array 2") do |i|
59+
n = 0
60+
while i > 0
61+
i-=1
62+
n += fixnum_array2.size
63+
end
64+
n
65+
end
66+
67+
bm.report("fixnum array 3") do |i|
68+
n = 0
69+
while i > 0
70+
i-=1
71+
n += fixnum_array3.size
72+
end
73+
n
74+
end
75+
76+
bm.report("fixnum array 10") do |i|
77+
n = 0
78+
while i > 0
79+
i-=1
80+
n += fixnum_array10.size
81+
end
82+
n
83+
end
84+
85+
bm.report("float array 1") do |i|
86+
n = 0
87+
while i > 0
88+
i-=1
89+
n += float_array1.size
90+
end
91+
n
92+
end
93+
94+
bm.report("float array 2") do |i|
95+
n = 0
96+
while i > 0
97+
i-=1
98+
n += float_array2.size
99+
end
100+
n
101+
end
102+
103+
bm.report("float array 3") do |i|
104+
n = 0
105+
while i > 0
106+
i-=1
107+
n += float_array3.size
108+
end
109+
n
110+
end
111+
112+
bm.report("float array 10") do |i|
113+
n = 0
114+
while i > 0
115+
i-=1
116+
n += float_array10.size
117+
end
118+
n
119+
end
120+
end

core/src/main/java/org/jruby/RubyArray.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,18 @@ public static RubyArray newArrayLight(Ruby runtime, IRubyObject obj) {
218218
return USE_PACKED_ARRAYS ? new RubyArrayOneObject(runtime, obj) : new RubyArray(runtime, arrayOf(obj));
219219
}
220220

221+
public static RubyArray newArrayLight(RubyClass arrayClass, IRubyObject obj) {
222+
return USE_PACKED_ARRAYS ? new RubyArrayOneObject(arrayClass, obj) : new RubyArray(arrayClass, arrayOf(obj), false);
223+
}
224+
221225
public static RubyArray newArrayLight(Ruby runtime, IRubyObject car, IRubyObject cdr) {
222226
return USE_PACKED_ARRAYS ? new RubyArrayTwoObject(runtime, car, cdr) : new RubyArray(runtime, arrayOf(car, cdr));
223227
}
224228

229+
public static RubyArray newArrayLight(RubyClass arrayClass, IRubyObject car, IRubyObject cdr) {
230+
return USE_PACKED_ARRAYS ? new RubyArrayTwoObject(arrayClass, car, cdr) : new RubyArray(arrayClass, arrayOf(car, cdr), false);
231+
}
232+
225233
public static RubyArray newArrayLight(Ruby runtime, IRubyObject... objs) {
226234
return new RubyArray(runtime, objs, false);
227235
}
@@ -275,6 +283,14 @@ public static RubyArray newArray(Ruby runtime, List<? extends IRubyObject> list)
275283
return isPackedArray(list) ? packedArray(runtime, list) : new RubyArray(runtime, list.toArray(IRubyObject.NULL_ARRAY));
276284
}
277285

286+
public static RubyArray newSharedArray(RubyClass arrayClass, IRubyObject[] shared) {
287+
RubyArray sharedArray = new RubyArray(arrayClass, shared, true);
288+
289+
sharedArray.isShared = true;
290+
291+
return sharedArray;
292+
}
293+
278294
private static RubyArray packedArray(final Ruby runtime, final IRubyObject[] args) {
279295
if (args.length == 1) {
280296
return new RubyArrayOneObject(runtime, args[0]);
@@ -450,6 +466,13 @@ public RubyArray(Ruby runtime, RubyClass klass, IRubyObject[] vals) {
450466
realLength = vals.length;
451467
}
452468

469+
public RubyArray(RubyClass klass, IRubyObject[] vals, boolean shared) {
470+
super(klass);
471+
values = vals;
472+
realLength = vals.length;
473+
isShared = shared;
474+
}
475+
453476
/**
454477
* Overridden by specialized arrays to fall back to IRubyObject[].
455478
*/

core/src/main/java/org/jruby/ir/targets/JVMVisitor.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,14 @@
8181
import java.io.ByteArrayOutputStream;
8282
import java.lang.invoke.MethodType;
8383
import java.util.ArrayList;
84+
import java.util.Arrays;
8485
import java.util.HashMap;
8586
import java.util.Iterator;
8687
import java.util.List;
8788
import java.util.Map;
89+
import java.util.stream.Stream;
8890

91+
import static java.util.Arrays.stream;
8992
import static org.jruby.util.CodegenUtils.c;
9093
import static org.jruby.util.CodegenUtils.ci;
9194
import static org.jruby.util.CodegenUtils.p;
@@ -2705,11 +2708,27 @@ public void GetEncodingInstr(GetEncodingInstr getencodinginstr) {
27052708
public void Array(Array array) {
27062709
jvmMethod().loadContext();
27072710

2708-
for (Operand operand : array.getElts()) {
2711+
Operand[] operands = array.getElts();
2712+
2713+
if (operands.length > 0) {
2714+
// TODO: these are iterating twice
2715+
if (stream(operands).allMatch(o -> o instanceof Fixnum)) {
2716+
List<Long> list = stream(operands).map(o -> ((Fixnum) o).getValue()).toList();
2717+
jvmMethod().getValueCompiler().pushFixnumArray(list);
2718+
return;
2719+
} else if (stream(operands).allMatch(o -> o instanceof Fixnum)) {
2720+
List<Double> list = stream(operands).map(o -> ((Float) o).getValue()).toList();
2721+
jvmMethod().getValueCompiler().pushFloatArray(list);
2722+
return;
2723+
}
2724+
}
2725+
2726+
// unoptimized path
2727+
for (Operand operand : operands) {
27092728
visit(operand);
27102729
}
27112730

2712-
jvmMethod().getDynamicValueCompiler().array(array.getElts().length);
2731+
jvmMethod().getDynamicValueCompiler().array(operands.length);
27132732
}
27142733

27152734
@Override

core/src/main/java/org/jruby/ir/targets/ValueCompiler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ public interface ValueCompiler {
234234

235235
void pushChilledString(ByteList byteList, int codeRange);
236236

237+
void pushFixnumArray(List<Long> values);
238+
239+
void pushFloatArray(List<Double> values);
240+
237241
enum DStringElementType { STRING, OTHER }
238242
record DStringElement<T>(DStringElementType type, T value) {}
239243

core/src/main/java/org/jruby/ir/targets/indy/ArrayBootstrap.java

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

33
import com.headius.invokebinder.Binder;
4+
import org.jruby.Ruby;
45
import org.jruby.RubyArray;
6+
import org.jruby.RubyClass;
7+
import org.jruby.runtime.Helpers;
58
import org.jruby.runtime.ThreadContext;
69
import org.jruby.runtime.builtin.IRubyObject;
10+
import org.jruby.specialized.RubyArrayOneObject;
711
import org.jruby.specialized.RubyArraySpecialized;
12+
import org.jruby.specialized.RubyArrayTwoObject;
13+
import org.jruby.util.func.ObjectObjectIntFunction;
814
import org.objectweb.asm.Handle;
915
import org.objectweb.asm.Opcodes;
1016

@@ -13,7 +19,10 @@
1319
import java.lang.invoke.MethodHandle;
1420
import java.lang.invoke.MethodHandles;
1521
import java.lang.invoke.MethodType;
22+
import java.lang.invoke.MutableCallSite;
23+
import java.util.stream.IntStream;
1624

25+
import static org.jruby.runtime.Helpers.arrayOf;
1726
import static org.jruby.util.CodegenUtils.p;
1827
import static org.jruby.util.CodegenUtils.sig;
1928

@@ -24,6 +33,12 @@ public class ArrayBootstrap {
2433
"array",
2534
sig(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class),
2635
false);
36+
public static final Handle NUMERIC_ARRAY = new Handle(
37+
Opcodes.H_INVOKESTATIC,
38+
p(ArrayBootstrap.class),
39+
"literalArray",
40+
sig(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class),
41+
false);
2742
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
2843

2944
private static final MethodHandle ARRAY_HANDLE =
@@ -45,4 +60,72 @@ public static RubyArray array(ThreadContext context, IRubyObject[] ary) {
4560
// array() only dispatches here if ^^ holds
4661
return RubyArray.newArrayNoCopy(context.runtime, ary);
4762
}
63+
64+
public static CallSite literalArray(MethodHandles.Lookup lookup, String name, MethodType type, String stringValues) {
65+
MutableCallSite site = new MutableCallSite(type);
66+
67+
Object values = switch (name) {
68+
case "fixnumArray" -> Helpers.decodeLongString(stringValues);
69+
case "floatArray" -> Helpers.decodeDoubleString(stringValues);
70+
default -> throw new RuntimeException("invalid literal array type");
71+
};
72+
73+
MethodHandle handle = Binder
74+
.from(type)
75+
.append(site, values)
76+
.invokeStaticQuiet(ArrayBootstrap.class, name);
77+
78+
site.setTarget(handle);
79+
80+
return site;
81+
}
82+
83+
public static RubyArray fixnumArray(ThreadContext context, MutableCallSite site, long[] values) {
84+
return bindArray(context, site, values, values.length,
85+
(runtime, vals, index) -> runtime.newFixnum(values[index]));
86+
}
87+
88+
public static RubyArray floatArray(ThreadContext context, MutableCallSite site, double[] values) {
89+
return bindArray(context, site, values, values.length,
90+
(runtime, vals, index) -> runtime.newFloat(values[index]));
91+
}
92+
93+
private static <ArrayType> RubyArray bindArray(ThreadContext context, MutableCallSite site, ArrayType values, int size, ObjectObjectIntFunction<Ruby, ArrayType, IRubyObject> mapper) {
94+
Ruby runtime = context.runtime;
95+
RubyClass arrayClass = runtime.getArray();
96+
97+
return switch (size) {
98+
case 1 -> bindArray(site, arrayClass, mapper.apply(runtime, values, 0));
99+
case 2 -> bindArray(site, arrayClass, mapper.apply(runtime, values, 0), mapper.apply(runtime, values, 1));
100+
default -> bindArray(site, arrayClass,
101+
IntStream.range(0, size).mapToObj(i -> mapper.apply(runtime, values, i)).toArray(IRubyObject[]::new));
102+
};
103+
}
104+
105+
private static RubyArray bindArray(MutableCallSite site, RubyClass arrayClass, IRubyObject car) {
106+
site.setTarget(Binder.from(site.type())
107+
.drop(0)
108+
.append(arrayOf(RubyClass.class, IRubyObject.class), arrayClass, car)
109+
.invokeConstructorQuiet(RubyArrayOneObject.class));
110+
111+
return new RubyArrayOneObject(arrayClass, car);
112+
}
113+
114+
private static RubyArray bindArray(MutableCallSite site, RubyClass arrayClass, IRubyObject car, IRubyObject cdr) {
115+
site.setTarget(Binder.from(site.type())
116+
.drop(0)
117+
.append(arrayOf(RubyClass.class, IRubyObject.class, IRubyObject.class), arrayClass, car, cdr)
118+
.invokeConstructorQuiet(RubyArrayTwoObject.class));
119+
120+
return new RubyArrayTwoObject(arrayClass, car, cdr);
121+
}
122+
123+
private static RubyArray bindArray(MutableCallSite site, RubyClass arrayClass, IRubyObject[] values) {
124+
site.setTarget(Binder.from(site.type())
125+
.drop(0)
126+
.append(arrayClass, values)
127+
.invokeStaticQuiet(RubyArray.class, "newSharedArray"));
128+
129+
return RubyArray.newSharedArray(arrayClass, values);
130+
}
48131
}

core/src/main/java/org/jruby/ir/targets/indy/IndyValueCompiler.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.jcodings.Encoding;
44
import org.jruby.Ruby;
5+
import org.jruby.RubyArray;
56
import org.jruby.RubyBignum;
67
import org.jruby.RubyClass;
78
import org.jruby.RubyEncoding;
@@ -18,6 +19,7 @@
1819
import org.jruby.ir.targets.ValueCompiler;
1920
import org.jruby.ir.targets.simple.NormalValueCompiler;
2021
import org.jruby.runtime.CallType;
22+
import org.jruby.runtime.Helpers;
2123
import org.jruby.runtime.ThreadContext;
2224
import org.jruby.runtime.builtin.IRubyObject;
2325
import org.jruby.runtime.callsite.CachingCallSite;
@@ -243,4 +245,19 @@ public void pushCallSite(String className, String siteName, String scopeFieldNam
243245
public void pushConstantLookupSite(String className, String siteName, ByteList name) {
244246
normalValueCompiler.pushConstantLookupSite(className, siteName, name);
245247
}
248+
249+
@Override
250+
public void pushFixnumArray(List<Long> values) {
251+
String fixnumString = Helpers.encodeLongString(values);
252+
253+
compiler.adapter.invokedynamic("fixnumArray", sig(RubyArray.class, ThreadContext.class), ArrayBootstrap.NUMERIC_ARRAY, fixnumString);
254+
}
255+
256+
@Override
257+
public void pushFloatArray(List<Double> values) {
258+
String doubleString = Helpers.encodeDoubleString(values);
259+
260+
compiler.adapter.invokedynamic("floatArray", sig(RubyArray.class, ThreadContext.class), ArrayBootstrap.NUMERIC_ARRAY, doubleString);
261+
}
262+
246263
}

core/src/main/java/org/jruby/ir/targets/simple/NormalValueCompiler.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,4 +507,16 @@ private static void keyFor(StringBuilder builder, Object obj) {
507507
}
508508

509509
private final Map<Object, String> cacheFieldNames = new HashMap<>();
510+
511+
public void pushFixnumArray(List<Long> values) {
512+
values.forEach(obj -> pushFixnum(obj.longValue()));
513+
514+
compiler.getDynamicValueCompiler().array(values.size());
515+
}
516+
517+
public void pushFloatArray(List<Double> values) {
518+
values.forEach(obj -> pushFloat(obj.doubleValue()));
519+
520+
compiler.getDynamicValueCompiler().array(values.size());
521+
}
510522
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy