Skip to content

Commit fad0e74

Browse files
committed
[GR-45118] Remove ArrayBuilderNode's expectedLength as it causes too many deoptimizations
* It would transferToInterpreterAndInvalidate() whenever it saw a bigger array at a given call site. But the only advantage of expectedLength is to avoid the branch for growing the array, which we can just BranchProfile and is very rare anyway given a good start(length). * Remove ArrayBuilderNode#start() without length as that is now unused.
1 parent 0ea2318 commit fad0e74

File tree

3 files changed

+58
-40
lines changed

3 files changed

+58
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ Performance:
127127
* Splitting (copying) of call targets has been optimized by implementing `cloneUninitialized()` (@andrykonchin, @eregon).
128128
* `Process.pid` is now cached per process like `$$` (#2882, @horakivo)
129129
* Use the system `libyaml` for `psych` to improve warmup when parsing YAML (#2089, @eregon).
130+
* Fixed repeated deoptimizations for methods building an `Array` which is growing over multiple calls at a given call site (@eregon).
130131

131132
Changes:
132133

src/main/java/org/truffleruby/core/array/ArrayBuilderNode.java

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
*/
1010
package org.truffleruby.core.array;
1111

12+
import com.oracle.truffle.api.dsl.Cached;
1213
import com.oracle.truffle.api.dsl.NeverDefault;
14+
import com.oracle.truffle.api.profiles.BranchProfile;
1315
import org.truffleruby.core.array.library.ArrayStoreLibrary;
1416
import org.truffleruby.core.array.library.ArrayStoreLibrary.ArrayAllocator;
1517
import org.truffleruby.core.array.ArrayBuilderNodeFactory.AppendArrayNodeGen;
@@ -48,8 +50,6 @@ public static ArrayBuilderNode create() {
4850
return new ArrayBuilderProxyNode();
4951
}
5052

51-
public abstract BuilderState start();
52-
5353
public abstract BuilderState start(int length);
5454

5555
public abstract void appendArray(BuilderState state, int index, RubyArray array);
@@ -60,15 +60,10 @@ public static ArrayBuilderNode create() {
6060

6161
private static class ArrayBuilderProxyNode extends ArrayBuilderNode {
6262

63-
@Child StartNode startNode = new StartNode(ArrayStoreLibrary.initialAllocator(false), 0);
63+
@Child StartNode startNode = new StartNode(ArrayStoreLibrary.initialAllocator(false));
6464
@Child AppendArrayNode appendArrayNode;
6565
@Child AppendOneNode appendOneNode;
6666

67-
@Override
68-
public BuilderState start() {
69-
return startNode.start();
70-
}
71-
7267
@Override
7368
public BuilderState start(int length) {
7469
return startNode.start(length);
@@ -106,7 +101,7 @@ private AppendOneNode getAppendOneNode() {
106101
return appendOneNode;
107102
}
108103

109-
public synchronized ArrayAllocator updateStrategy(ArrayStoreLibrary.ArrayAllocator newStrategy, int newLength) {
104+
public synchronized ArrayAllocator updateStrategy(ArrayStoreLibrary.ArrayAllocator newStrategy) {
110105
final ArrayStoreLibrary.ArrayAllocator oldStrategy = startNode.allocator;
111106
final ArrayStoreLibrary.ArrayAllocator updatedAllocator;
112107
// If two threads have raced to update the strategy then
@@ -122,11 +117,8 @@ public synchronized ArrayAllocator updateStrategy(ArrayStoreLibrary.ArrayAllocat
122117
updatedAllocator = oldStrategy;
123118
}
124119

125-
final int oldLength = startNode.expectedLength;
126-
final int newExpectedLength = Math.max(oldLength, newLength);
127-
128-
if (updatedAllocator != oldStrategy || newExpectedLength > oldLength) {
129-
startNode.replace(new StartNode(updatedAllocator, newExpectedLength));
120+
if (updatedAllocator != oldStrategy) {
121+
startNode.replace(new StartNode(updatedAllocator));
130122
}
131123

132124
if (newStrategy != oldStrategy) {
@@ -145,35 +137,21 @@ public synchronized ArrayAllocator updateStrategy(ArrayStoreLibrary.ArrayAllocat
145137

146138
public abstract static class ArrayBuilderBaseNode extends RubyBaseNode {
147139

148-
protected ArrayAllocator replaceNodes(ArrayStoreLibrary.ArrayAllocator strategy, int size) {
140+
protected ArrayAllocator replaceNodes(ArrayStoreLibrary.ArrayAllocator strategy) {
149141
final ArrayBuilderProxyNode parent = (ArrayBuilderProxyNode) getParent();
150-
return parent.updateStrategy(strategy, size);
142+
return parent.updateStrategy(strategy);
151143
}
152144
}
153145

154146
public static class StartNode extends ArrayBuilderBaseNode {
155147

156148
private final ArrayStoreLibrary.ArrayAllocator allocator;
157-
private final int expectedLength;
158149

159-
public StartNode(ArrayStoreLibrary.ArrayAllocator allocator, int expectedLength) {
150+
public StartNode(ArrayStoreLibrary.ArrayAllocator allocator) {
160151
this.allocator = allocator;
161-
this.expectedLength = expectedLength;
162-
}
163-
164-
public BuilderState start() {
165-
if (allocator == ArrayStoreLibrary.initialAllocator(false)) {
166-
return new BuilderState(allocator.allocate(0), expectedLength);
167-
} else {
168-
return new BuilderState(allocator.allocate(expectedLength), expectedLength);
169-
}
170152
}
171153

172154
public BuilderState start(int length) {
173-
if (length > expectedLength) {
174-
CompilerDirectives.transferToInterpreterAndInvalidate();
175-
replaceNodes(allocator, length);
176-
}
177155
if (allocator == ArrayStoreLibrary.initialAllocator(false)) {
178156
return new BuilderState(allocator.allocate(0), length);
179157
} else {
@@ -196,15 +174,15 @@ public static AppendOneNode create() {
196174
limit = "1")
197175
protected void appendCompatibleType(BuilderState state, int index, Object value,
198176
@Bind("state.store") Object store,
199-
@CachedLibrary("store") ArrayStoreLibrary arrays) {
177+
@CachedLibrary("store") ArrayStoreLibrary arrays,
178+
@Cached BranchProfile growProfile) {
200179
assert state.nextIndex == index;
201180
final int length = arrays.capacity(state.store);
202181
if (index >= length) {
203-
CompilerDirectives.transferToInterpreterAndInvalidate();
182+
growProfile.enter();
204183
final int capacity = ArrayUtils.capacityForOneMore(getLanguage(), length);
205184
state.store = arrays.expand(state.store, capacity);
206185
state.capacity = capacity;
207-
replaceNodes(arrays.unsharedAllocator(state.store), capacity);
208186
}
209187
arrays.write(state.store, index, value);
210188
state.nextIndex++;
@@ -229,7 +207,7 @@ protected void appendNewStrategy(BuilderState state, int index, Object value,
229207
neededCapacity = currentCapacity;
230208
}
231209

232-
newAllocator = replaceNodes(newAllocator, neededCapacity);
210+
newAllocator = replaceNodes(newAllocator);
233211

234212
final Object newStore = newAllocator.allocate(neededCapacity);
235213
stores.copyContents(state.store, 0, newStore, 0, index);
@@ -258,15 +236,15 @@ protected void appendCompatibleStrategy(BuilderState state, int index, RubyArray
258236
@Bind("state.store") Object store,
259237
@Bind("other.getStore()") Object otherStore,
260238
@CachedLibrary("store") ArrayStoreLibrary arrays,
261-
@CachedLibrary("otherStore") ArrayStoreLibrary others) {
239+
@CachedLibrary("otherStore") ArrayStoreLibrary others,
240+
@Cached BranchProfile growProfile) {
262241
assert state.nextIndex == index;
263242
final int otherSize = other.size;
264243
final int neededSize = index + otherSize;
265244

266245
int length = arrays.capacity(state.store);
267246
if (neededSize > length) {
268-
CompilerDirectives.transferToInterpreterAndInvalidate();
269-
replaceNodes(arrays.unsharedAllocator(state.store), neededSize);
247+
growProfile.enter();
270248
final int capacity = ArrayUtils.capacity(getLanguage(), length, neededSize);
271249
state.store = arrays.expand(state.store, capacity);
272250
state.capacity = capacity;
@@ -300,8 +278,7 @@ protected void appendNewStrategy(BuilderState state, int index, RubyArray other,
300278
}
301279

302280
ArrayAllocator allocator = replaceNodes(
303-
newArrayLibrary.generalizeForStore(state.store, other.getStore()),
304-
neededCapacity);
281+
newArrayLibrary.generalizeForStore(state.store, other.getStore()));
305282
newStore = allocator.allocate(neededCapacity);
306283

307284
newArrayLibrary.copyContents(state.store, 0, newStore, 0, index);

src/test/java/org/truffleruby/ArrayBuilderTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.io.InputStream;
1818
import java.io.InputStreamReader;
1919
import java.io.Reader;
20+
import java.util.Arrays;
2021

2122
import com.oracle.truffle.api.frame.VirtualFrame;
2223
import com.oracle.truffle.api.nodes.Node;
@@ -31,6 +32,7 @@
3132
import org.truffleruby.core.array.RubyArray;
3233
import org.truffleruby.core.array.ArrayBuilderNode.BuilderState;
3334
import org.truffleruby.core.array.library.ArrayStoreLibrary;
35+
import org.truffleruby.language.Nil;
3436
import org.truffleruby.shared.TruffleRuby;
3537

3638
public class ArrayBuilderTest {
@@ -105,6 +107,22 @@ public void arrayBuilderAppendObjectTest() {
105107
});
106108
}
107109

110+
@Test
111+
public void arrayBuilderAppendGrowTest() {
112+
testInContext(() -> {
113+
ArrayBuilderNode builder = createBuilder();
114+
BuilderState state = builder.start(10);
115+
for (int i = 0; i < 12; i++) {
116+
builder.appendValue(state, i, Nil.INSTANCE);
117+
}
118+
Object[] result = (Object[]) builder.finish(state, 12);
119+
for (int i = 0; i < 12; i++) {
120+
Object e = result[i];
121+
assertEquals(Nil.INSTANCE, e);
122+
}
123+
});
124+
}
125+
108126
@Test
109127
public void arrayBuilderAppendEmptyArrayTest() {
110128
testInContext(() -> {
@@ -180,6 +198,28 @@ public void arrayBuilderAppendObjectArrayTest() {
180198
});
181199
}
182200

201+
@Test
202+
public void arrayBuilderAppendGrowArrayTest() {
203+
testInContext(() -> {
204+
ArrayBuilderNode builder = createBuilder();
205+
BuilderState state = builder.start(10);
206+
Object[] array = new Object[6];
207+
Arrays.fill(array, Nil.INSTANCE);
208+
RubyArray otherStore = new RubyArray(
209+
RubyLanguage.getCurrentContext().getCoreLibrary().arrayClass,
210+
RubyLanguage.getCurrentLanguage().arrayShape,
211+
array,
212+
array.length);
213+
builder.appendArray(state, 0, otherStore);
214+
builder.appendArray(state, 6, otherStore);
215+
Object[] result = (Object[]) builder.finish(state, 12);
216+
for (int i = 0; i < 12; i++) {
217+
Object e = result[i];
218+
assertEquals(Nil.INSTANCE, e);
219+
}
220+
});
221+
}
222+
183223
private ArrayBuilderNode createBuilder() {
184224
return adopt(ArrayBuilderNode.create());
185225
}

0 commit comments

Comments
 (0)