Skip to content

Commit 6432f0b

Browse files
committed
Avoid prefilling subclasses array with nil until end
We know we'll fill or nearly fill the array with other references, so pre-filling with nil is wasted effort. This change introduces a "raw" array sequence: create raw array with default nulls, fill with values, then replace remaining nulls with nil.
1 parent 3708851 commit 6432f0b

File tree

2 files changed

+43
-13
lines changed

2 files changed

+43
-13
lines changed

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
import static org.jruby.runtime.Helpers.addBufferLength;
9696
import static org.jruby.runtime.Helpers.arrayOf;
9797
import static org.jruby.runtime.Helpers.calculateBufferLength;
98+
import static org.jruby.runtime.Helpers.fillNil;
9899
import static org.jruby.runtime.Helpers.hashEnd;
99100
import static org.jruby.runtime.Helpers.murmurCombine;
100101
import static org.jruby.runtime.Helpers.validateBufferLength;
@@ -193,6 +194,30 @@ public static final RubyArray newArrayLight(final Ruby runtime, final int len) {
193194
return new RubyArray(runtime, runtime.getArray(), values, 0, 0, false);
194195
}
195196

197+
/**
198+
* Construct an array with the specified backing storage length. The array must be filled with non-null values
199+
* before entering Rubyspace.
200+
*
201+
* @param context the current context
202+
* @param len the length of the array buffer requested
203+
* @return an array with the given buffer size, entries initialized to null
204+
*/
205+
public static RubyArray newArrayRaw(final ThreadContext context, final int len) {
206+
Ruby runtime = context.runtime;
207+
return new RubyArray(runtime, runtime.getArray(), IRubyObject.array(len), 0, 0, false);
208+
}
209+
210+
/**
211+
* Fill the remaining array slots with the given value. Pair with newArrayRaw to reduce the cost of setting up a new array.
212+
*
213+
*/
214+
public void fillRestWithNil(final ThreadContext context) {
215+
int realLength = this.realLength;
216+
IRubyObject[] values = this.values;
217+
if (realLength == values.length) return;
218+
fillNil(values, realLength, values.length, context.runtime);
219+
}
220+
196221
/** rb_ary_new
197222
*
198223
*/

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

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ public IRubyObject initialize_copy(IRubyObject original) {
10141014
public IRubyObject subclasses(ThreadContext context) {
10151015
RubyArray<RubyClass> subs = newConcreteSubclassesArray(context);
10161016
int clearedCount = concreteSubclasses(subs);
1017-
finishConcreteSubclasses(subs, clearedCount);
1017+
finishConcreteSubclasses(context, subs, clearedCount);
10181018

10191019
return subs;
10201020
}
@@ -1059,7 +1059,7 @@ public Collection<RubyClass> subclasses(boolean includeDescendants) {
10591059
}
10601060

10611061
private RubyArray<RubyClass> newConcreteSubclassesArray(ThreadContext context) {
1062-
RubyArray<RubyClass> subs = RubyArray.newArray(context.runtime, this.concreteSubclassesEstimate);
1062+
RubyArray<RubyClass> subs = RubyArray.newArrayRaw(context, this.concreteSubclassesEstimate);
10631063
return subs;
10641064
}
10651065

@@ -1103,39 +1103,44 @@ private void finishSubclasses(Collection<RubyClass> mine, int clearedCount, bool
11031103

11041104
private int concreteSubclasses(RubyArray<RubyClass> subs) {
11051105
SubclassNode subclassNode = this.subclassNode;
1106+
1107+
// skip first entry if not concrete
1108+
if (!subclassNode.concrete) subclassNode = subclassNode.nextConcrete;
1109+
11061110
int clearedCount = 0;
11071111
while (subclassNode != null) {
11081112
RubyClass klass = subclassNode.ref.get();
11091113
subclassNode = subclassNode.nextConcrete;
11101114

11111115
if (klass == null) {
11121116
clearedCount++;
1113-
continue;
1117+
} else {
1118+
processConcreteSubclass(subs, klass);
11141119
}
11151120

1116-
processConcreteSubclass(subs, klass);
11171121
}
11181122
return clearedCount;
11191123
}
11201124

11211125
private static void processConcreteSubclass(RubyArray<RubyClass> subs, RubyClass klass) {
1122-
if (!klass.isSingleton()) {
1123-
if (klass.isIncluded() || klass.isPrepended()) {
1124-
klass.concreteSubclasses(subs);
1125-
} else {
1126-
subs.append(klass);
1127-
}
1126+
assert !klass.isSingleton();
1127+
1128+
if (klass.isIncluded() || klass.isPrepended()) {
1129+
klass.concreteSubclasses(subs);
1130+
} else {
1131+
subs.append(klass);
11281132
}
11291133
}
11301134

1131-
private void finishConcreteSubclasses(RubyArray<RubyClass> subs, int clearedCount) {
1135+
private void finishConcreteSubclasses(ThreadContext context, RubyArray<RubyClass> subs, int clearedCount) {
1136+
subs.fillRestWithNil(context);
11321137
int newSize = subs.size();
1133-
concreteSubclassesEstimate = newSize + 4;
1138+
concreteSubclassesEstimate = newSize;
11341139
cleanSubclasses(newSize, clearedCount);
11351140
}
11361141

11371142
private void cleanSubclasses(int size, int vacated) {
1138-
// tidy up if more than 25% cleared references
1143+
// tidy up if more than threshold of cleared references
11391144
if ((double) vacated / size > SUBCLASSES_CLEAN_FACTOR) {
11401145
SubclassNode subclassNode = this.subclassNode;
11411146
SubclassNode newTop = rebuildSubclasses(subclassNode);

0 commit comments

Comments
 (0)