Skip to content

Commit 2976caf

Browse files
committed
Refactor subclasses logic for performance
* Split large methods into smaller ones. * Eliminate repeated null-checking. * Always use estimate to create array/list, but add fast path to newArray that reuses zero-length array. * Add estimates for "all subclasses" and "all descendants" used by the old form. * Only update estimates for the target class, not for subclasses. This refactored impl is consistently faster than 9.4.9.0: 9.4.9.0: 1 thread Numeric.subclasses 1.281k (±10.4%) i/s - 6.348k in 5.015677s 1 thread Object.subclasses 57.555 (± 1.7%) i/s - 290.000 in 5.042078s 5 thread Numeric.subclasses 1.866k (±12.8%) i/s - 9.243k in 5.026434s 5 thread Object.subclasses 156.215 (± 3.8%) i/s - 780.000 in 5.000381s 10 thread Numeric.subclasses 1.151k (±11.8%) i/s - 5.700k in 5.027341s 10 thread Object.subclasses 182.022 (±15.9%) i/s - 864.000 in 5.076040s 50 thread Numeric.subclasses 276.258 (±33.3%) i/s - 1.122k in 5.030777s 50 thread Object.subclasses 149.436 (±16.1%) i/s - 705.000 in 5.005946s New impl: 1 thread Numeric.subclasses 1.871k (± 9.9%) i/s - 9.313k in 5.028326s 1 thread Object.subclasses 194.344 (± 2.6%) i/s - 988.000 in 5.087741s 5 thread Numeric.subclasses 1.966k (±12.9%) i/s - 9.750k in 5.044797s 5 thread Object.subclasses 475.950 (± 8.4%) i/s - 2.392k in 5.065777s 10 thread Numeric.subclasses 1.170k (±16.9%) i/s - 5.720k in 5.238812s 10 thread Object.subclasses 356.624 (±41.5%) i/s - 1.470k in 5.065979s 50 thread Numeric.subclasses 338.594 (±13.9%) i/s - 1.665k in 5.075536s 50 thread Object.subclasses 251.153 (±10.4%) i/s - 1.250k in 5.025052s
1 parent 57d8940 commit 2976caf

File tree

2 files changed

+85
-61
lines changed

2 files changed

+85
-61
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ public static final RubyArray newArrayLight(final Ruby runtime, final long len)
178178
}
179179

180180
public static final RubyArray newArray(final Ruby runtime, final int len) {
181+
if (len == 0) {
182+
return newEmptyArray(runtime);
183+
}
184+
181185
IRubyObject[] values = IRubyObject.array(validateBufferLength(runtime, len));
182186
Helpers.fillNil(values, 0, len, runtime);
183187
return new RubyArray(runtime, values, 0, 0);

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

Lines changed: 81 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
public class RubyClass extends RubyModule {
112112

113113
private static final Logger LOG = LoggerFactory.getLogger(RubyClass.class);
114+
private static final double SUBCLASSES_CLEAN_FACTOR = 0.25;
114115

115116
public static void createClassClass(Ruby runtime, RubyClass classClass) {
116117
classClass.setClassIndex(ClassIndex.CLASS);
@@ -1011,11 +1012,9 @@ public IRubyObject initialize_copy(IRubyObject original) {
10111012

10121013
@JRubyMethod
10131014
public IRubyObject subclasses(ThreadContext context) {
1014-
int subclassEstimate = this.subclassEstimate;
1015-
1016-
RubyArray<RubyClass> subs = RubyArray.newArray(context.runtime, subclassEstimate == -1 ? 4 : subclassEstimate);
1017-
1018-
concreteSubclasses(subs);
1015+
RubyArray<RubyClass> subs = newConcreteSubclassesArray(context);
1016+
int clearedCount = concreteSubclasses(subs);
1017+
finishConcreteSubclasses(subs, clearedCount);
10191018

10201019
return subs;
10211020
}
@@ -1052,79 +1051,98 @@ public final Collection<RubyClass> subclasses() {
10521051
}
10531052

10541053
public Collection<RubyClass> subclasses(boolean includeDescendants) {
1055-
SubclassNode subclassNode = this.subclassNode;
1056-
if (subclassNode != null) {
1057-
Collection<RubyClass> mine = new ArrayList<>();
1058-
subclassesInner(mine, includeDescendants);
1054+
Collection<RubyClass> mine = newSubclassesList(includeDescendants);
1055+
int clearedCount = subclassesInner(mine, includeDescendants);
1056+
finishSubclasses(mine, clearedCount, includeDescendants);
10591057

1060-
return mine;
1061-
}
1062-
return Collections.EMPTY_LIST;
1058+
return mine;
10631059
}
10641060

1065-
private void subclassesInner(Collection<RubyClass> mine, boolean includeDescendants) {
1066-
SubclassNode subclassNode = this.subclassNode;
1067-
if (subclassNode != null) {
1068-
int clearedCount = 0;
1069-
while (subclassNode != null) {
1070-
RubyClass klass = subclassNode.ref.get();
1071-
subclassNode = subclassNode.next;
1072-
1073-
if (klass == null) {
1074-
clearedCount++;
1075-
continue;
1076-
}
1061+
private RubyArray<RubyClass> newConcreteSubclassesArray(ThreadContext context) {
1062+
RubyArray<RubyClass> subs = RubyArray.newArray(context.runtime, this.concreteSubclassesEstimate);
1063+
return subs;
1064+
}
1065+
1066+
private Collection<RubyClass> newSubclassesList(boolean includeDescendants) {
1067+
Collection<RubyClass> mine = new ArrayList<>(includeDescendants ? allDescendantsEstimate : allSubclassesEstimate);
1068+
return mine;
1069+
}
10771070

1078-
mine.add(klass);
1071+
private int subclassesInner(Collection<RubyClass> mine, boolean includeDescendants) {
1072+
SubclassNode subclassNode = this.subclassNode;
1073+
int clearedCount = 0;
1074+
while (subclassNode != null) {
1075+
RubyClass klass = subclassNode.ref.get();
1076+
subclassNode = subclassNode.next;
10791077

1080-
if (includeDescendants) klass.subclassesInner(mine, includeDescendants);
1078+
if (klass == null) {
1079+
clearedCount++;
1080+
continue;
10811081
}
1082-
int newSize = mine.size();
10831082

1084-
// tidy up if more than 25% cleared references
1085-
if ((double) clearedCount / newSize > 0.25) {
1086-
cleanSubclasses();
1087-
}
1083+
processSubclass(mine, includeDescendants, klass);
1084+
}
1085+
return clearedCount;
1086+
}
1087+
1088+
private static void processSubclass(Collection<RubyClass> mine, boolean includeDescendants, RubyClass klass) {
1089+
mine.add(klass);
1090+
1091+
if (includeDescendants) klass.subclassesInner(mine, includeDescendants);
1092+
}
1093+
1094+
private void finishSubclasses(Collection<RubyClass> mine, int clearedCount, boolean includeDescendants) {
1095+
int newSize = mine.size();
1096+
if (includeDescendants) {
1097+
allDescendantsEstimate = newSize;
1098+
} else {
1099+
allSubclassesEstimate = newSize;
10881100
}
1101+
cleanSubclasses(newSize, clearedCount);
10891102
}
10901103

1091-
private void concreteSubclasses(RubyArray<RubyClass> subs) {
1104+
private int concreteSubclasses(RubyArray<RubyClass> subs) {
10921105
SubclassNode subclassNode = this.subclassNode;
1093-
if (subclassNode != null) {
1094-
int clearedCount = 0;
1095-
while (subclassNode != null) {
1096-
RubyClass klass = subclassNode.ref.get();
1097-
subclassNode = subclassNode.next;
1098-
1099-
if (klass == null) {
1100-
clearedCount++;
1101-
continue;
1102-
}
1106+
int clearedCount = 0;
1107+
while (subclassNode != null) {
1108+
RubyClass klass = subclassNode.ref.get();
1109+
subclassNode = subclassNode.next;
11031110

1104-
if (!klass.isSingleton()) {
1105-
if (klass.isIncluded() || klass.isPrepended()) {
1106-
klass.concreteSubclasses(subs);
1107-
} else {
1108-
subs.append(klass);
1109-
}
1110-
}
1111+
if (klass == null) {
1112+
clearedCount++;
1113+
continue;
11111114
}
1112-
int newSize = subs.size();
1113-
subclassEstimate = newSize + 4;
11141115

1115-
// tidy up if more than 25% cleared references
1116-
if ((double) clearedCount / newSize > 0.25) {
1117-
cleanSubclasses();
1116+
processConcreteSubclass(subs, klass);
1117+
}
1118+
return clearedCount;
1119+
}
1120+
1121+
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);
11181127
}
11191128
}
11201129
}
11211130

1122-
private void cleanSubclasses() {
1123-
SubclassNode subclassNode = this.subclassNode;
1124-
SubclassNode newTop = rebuildSubclasses(subclassNode);
1125-
while (!SUBCLASS_UPDATER.compareAndSet(this, subclassNode, newTop)) {
1126-
subclassNode = this.subclassNode;
1127-
newTop = rebuildSubclasses(subclassNode);
1131+
private void finishConcreteSubclasses(RubyArray<RubyClass> subs, int clearedCount) {
1132+
int newSize = subs.size();
1133+
concreteSubclassesEstimate = newSize + 4;
1134+
cleanSubclasses(newSize, clearedCount);
1135+
}
1136+
1137+
private void cleanSubclasses(int size, int vacated) {
1138+
// tidy up if more than 25% cleared references
1139+
if ((double) vacated / size > SUBCLASSES_CLEAN_FACTOR) {
1140+
SubclassNode subclassNode = this.subclassNode;
1141+
SubclassNode newTop = rebuildSubclasses(subclassNode);
1142+
while (!SUBCLASS_UPDATER.compareAndSet(this, subclassNode, newTop)) {
1143+
subclassNode = this.subclassNode;
1144+
newTop = rebuildSubclasses(subclassNode);
1145+
}
11281146
}
11291147
}
11301148

@@ -3158,7 +3176,9 @@ public IRubyObject invokeFrom(ThreadContext context, CallType callType, IRubyObj
31583176
protected ObjectMarshal marshal;
31593177
private volatile SubclassNode subclassNode;
31603178
private static final AtomicReferenceFieldUpdater SUBCLASS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(RubyClass.class, SubclassNode.class, "subclassNode");
3161-
private int subclassEstimate = -1;
3179+
private int concreteSubclassesEstimate = 4;
3180+
private int allDescendantsEstimate = 4;
3181+
private int allSubclassesEstimate = 4;
31623182
public static final int CS_IDX_INITIALIZE = 0;
31633183
public enum CS_NAMES {
31643184
INITIALIZE("initialize");

0 commit comments

Comments
 (0)