Skip to content

Commit 8762037

Browse files
committed
[GR-17457] Reduce overhead of copying splatted args and improve RubyCallNode size.
PullRequest: truffleruby/3156
2 parents 92d8e87 + 3b1e9e0 commit 8762037

File tree

5 files changed

+97
-47
lines changed

5 files changed

+97
-47
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class Callee
2+
def call(*args)
3+
:foo
4+
end
5+
end
6+
7+
callees = Array.new(1000) { Callee.new }
8+
args = Array.new(1000) { |i| Array.new(i % 4, 1) }
9+
10+
benchmark 'dispatch-mono-splat-rest' do
11+
i = 0
12+
while i < 1000
13+
callees[i].call(*args[i])
14+
i += 1
15+
end
16+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class Callee
2+
def call(arg1 = nil, arg2 = nil, arg3 = nil, arg4 = nil)
3+
:foo
4+
end
5+
end
6+
7+
callees = Array.new(1000) { Callee.new }
8+
args = Array.new(1000) { |i| Array.new(i % 4, 1) }
9+
10+
benchmark 'dispatch-mono-splat' do
11+
i = 0
12+
while i < 1000
13+
callees[i].call(*args[i])
14+
i += 1
15+
end
16+
end

src/main/java/org/truffleruby/language/arguments/RubyArguments.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ private enum ArgumentIndicies {
4343
// user arguments follow, each RubyGuards.assertIsValidRubyValue
4444
}
4545

46-
private static final int RUNTIME_ARGUMENT_COUNT = ArgumentIndicies.values().length;
46+
static final int RUNTIME_ARGUMENT_COUNT = ArgumentIndicies.values().length;
4747

4848
public static boolean assertFrameArguments(Object[] arguments) {
4949
assert arguments.length >= RUNTIME_ARGUMENT_COUNT;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.language.arguments;
11+
12+
import com.oracle.truffle.api.nodes.Node.Child;
13+
import com.oracle.truffle.api.profiles.IntValueProfile;
14+
15+
import org.truffleruby.core.array.ArrayGuards;
16+
import org.truffleruby.core.array.RubyArray;
17+
import org.truffleruby.core.array.library.ArrayStoreLibrary;
18+
import org.truffleruby.language.RubyBaseNode;
19+
20+
public class SplatToArgsNode extends RubyBaseNode {
21+
22+
@Child protected ArrayStoreLibrary stores;
23+
24+
final IntValueProfile splatSizeProfile = IntValueProfile.createIdentityProfile();
25+
26+
public SplatToArgsNode() {
27+
stores = ArrayStoreLibrary.getFactory().createDispatched(ArrayGuards.storageStrategyLimit());
28+
}
29+
30+
public Object[] execute(Object receiver, Object[] rubyArgs, RubyArray splatted) {
31+
int size = splatSizeProfile.profile(splatted.size);
32+
Object store = splatted.store;
33+
Object[] newArgs = RubyArguments.repack(rubyArgs, receiver, 0, size, 0);
34+
stores.copyContents(store, 0, newArgs, RubyArguments.RUNTIME_ARGUMENT_COUNT, size);
35+
return newArgs;
36+
}
37+
}

src/main/java/org/truffleruby/language/dispatch/RubyCallNode.java

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
import org.truffleruby.RubyContext;
1313
import org.truffleruby.RubyLanguage;
1414
import org.truffleruby.core.array.ArrayAppendOneNode;
15-
import org.truffleruby.core.array.ArrayToObjectArrayNode;
16-
import org.truffleruby.core.array.ArrayToObjectArrayNodeGen;
1715
import org.truffleruby.core.array.AssignableNode;
16+
import org.truffleruby.core.array.RubyArray;
1817
import org.truffleruby.core.cast.BooleanCastNode;
1918
import org.truffleruby.core.cast.BooleanCastNodeGen;
2019
import org.truffleruby.core.inlined.LambdaToProcNode;
@@ -25,6 +24,7 @@
2524
import org.truffleruby.language.RubyGuards;
2625
import org.truffleruby.language.RubyNode;
2726
import org.truffleruby.language.arguments.RubyArguments;
27+
import org.truffleruby.language.arguments.SplatToArgsNode;
2828
import org.truffleruby.language.literal.NilLiteralNode;
2929
import org.truffleruby.language.methods.BlockDefinitionNode;
3030
import org.truffleruby.language.methods.InternalMethod;
@@ -58,11 +58,12 @@ public class RubyCallNode extends RubyContextSourceNode implements AssignableNod
5858
private final boolean isAttrAssign;
5959

6060
@Child private DispatchNode dispatch;
61-
@Child private ArrayToObjectArrayNode toObjectArrayNode;
6261
@Child private DefinedNode definedNode;
6362

6463
private final ConditionProfile nilProfile;
6564

65+
@Child private SplatToArgsNode splatToArgs;
66+
6667
public RubyCallNode(RubyCallNodeParameters parameters) {
6768
this.methodName = parameters.getMethodName();
6869
this.receiver = parameters.getReceiver();
@@ -91,25 +92,18 @@ public Object execute(VirtualFrame frame) {
9192
if (isSafeNavigation && nilProfile.profile(receiverObject == nil)) {
9293
return nil;
9394
}
94-
if (!isSplatted) {
95-
final Object[] rubyArgs = RubyArguments.allocate(arguments.length);
96-
RubyArguments.setSelf(rubyArgs, receiverObject);
97-
98-
executeArguments(frame, rubyArgs);
99-
100-
RubyArguments.setBlock(rubyArgs, executeBlock(frame));
101-
102-
return executeWithArgumentsEvaluated(frame, receiverObject, rubyArgs);
103-
} else {
104-
final Object[] executedArguments = executeArguments(frame);
95+
Object[] rubyArgs = RubyArguments.allocate(arguments.length);
96+
RubyArguments.setSelf(rubyArgs, receiverObject);
10597

106-
final Object blockObject = executeBlock(frame);
98+
executeArguments(frame, rubyArgs);
10799

108-
// The expansion of the splat is done after executing the block, for m(*args, &args.pop)
109-
final Object[] argumentsObjects = splat(executedArguments);
100+
RubyArguments.setBlock(rubyArgs, executeBlock(frame));
110101

111-
return executeWithArgumentsEvaluated(frame, receiverObject, blockObject, argumentsObjects);
102+
// The expansion of the splat is done after executing the block, for m(*args, &args.pop)
103+
if (isSplatted) {
104+
rubyArgs = splatArgs(receiverObject, rubyArgs);
112105
}
106+
return executeWithArgumentsEvaluated(frame, receiverObject, rubyArgs);
113107
}
114108

115109
@Override
@@ -121,24 +115,22 @@ public void assign(VirtualFrame frame, Object value) {
121115
if (isSafeNavigation && nilProfile.profile(receiverObject == nil)) {
122116
return;
123117
}
118+
Object[] rubyArgs = RubyArguments.allocate(arguments.length);
119+
RubyArguments.setSelf(rubyArgs, receiverObject);
124120

125-
final Object[] executedArguments = executeArguments(frame);
121+
executeArguments(frame, rubyArgs);
126122

127-
final Object blockObject = executeBlock(frame);
123+
RubyArguments.setBlock(rubyArgs, executeBlock(frame));
128124

129-
final Object[] argumentsObjects;
130125
if (isSplatted) {
131-
// The expansion of the splat is done after executing the block, for m(*args, &args.pop)
132-
argumentsObjects = splat(executedArguments);
133-
assert argumentsObjects[argumentsObjects.length - 1] == nil;
134-
argumentsObjects[argumentsObjects.length - 1] = value;
135-
} else {
136-
assert executedArguments[arguments.length - 1] == nil;
137-
executedArguments[arguments.length - 1] = value;
138-
argumentsObjects = executedArguments;
126+
rubyArgs = splatArgs(receiverObject, rubyArgs);
139127
}
140128

141-
executeWithArgumentsEvaluated(frame, receiverObject, blockObject, argumentsObjects);
129+
int argCount = RubyArguments.getArgumentsCount(rubyArgs);
130+
assert RubyArguments.getArgument(rubyArgs, argCount - 1) == nil;
131+
RubyArguments.setArgument(rubyArgs, argCount - 1, value);
132+
133+
executeWithArgumentsEvaluated(frame, receiverObject, rubyArgs);
142134
}
143135

144136
public Object executeWithArgumentsEvaluated(VirtualFrame frame, Object receiverObject, Object[] rubyArgs) {
@@ -175,31 +167,20 @@ private Object executeBlock(VirtualFrame frame) {
175167
}
176168
}
177169

178-
@ExplodeLoop
179-
private Object[] executeArguments(VirtualFrame frame) {
180-
final Object[] argumentsObjects = new Object[arguments.length];
181-
182-
for (int i = 0; i < arguments.length; i++) {
183-
argumentsObjects[i] = arguments[i].execute(frame);
184-
}
185-
186-
return argumentsObjects;
187-
}
188-
189170
@ExplodeLoop
190171
private void executeArguments(VirtualFrame frame, Object[] rubyArgs) {
191172
for (int i = 0; i < arguments.length; i++) {
192173
RubyArguments.setArgument(rubyArgs, i, arguments[i].execute(frame));
193174
}
194175
}
195176

196-
private Object[] splat(Object[] arguments) {
197-
if (toObjectArrayNode == null) {
177+
private Object[] splatArgs(Object receiverObject, Object[] rubyArgs) {
178+
if (splatToArgs == null) {
198179
CompilerDirectives.transferToInterpreterAndInvalidate();
199-
toObjectArrayNode = insert(ArrayToObjectArrayNodeGen.create());
180+
splatToArgs = insert(new SplatToArgsNode());
200181
}
201-
// TODO(CS): what happens if it isn't an Array?
202-
return toObjectArrayNode.unsplat(arguments);
182+
183+
return splatToArgs.execute(receiverObject, rubyArgs, (RubyArray) RubyArguments.getArgument(rubyArgs, 0));
203184
}
204185

205186
@Override

0 commit comments

Comments
 (0)