Skip to content

Commit 422bb1c

Browse files
authored
Merge pull request jruby#8462 from headius/subclasses_optz
Optimize Class#subclasses
2 parents ab5fc4d + 2976caf commit 422bb1c

File tree

4 files changed

+198
-75
lines changed

4 files changed

+198
-75
lines changed

bench/bench_subclasses.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require 'benchmark/ips'
2+
3+
Benchmark.ips do |bm|
4+
[1, 5, 10, 50].each do |count|
5+
bm.report("#{count} thread Numeric.subclasses") do
6+
count.times.map {
7+
Thread.new {
8+
i = 10_000 / count
9+
while i > 0
10+
Numeric.subclasses
11+
i-=1
12+
end
13+
}
14+
}.each(&:join)
15+
end
16+
bm.report("#{count} thread Object.subclasses") do
17+
count.times.map {
18+
Thread.new {
19+
i = 10_000 / count
20+
while i > 0
21+
Object.subclasses
22+
i-=1
23+
end
24+
}
25+
}.each(&:join)
26+
end
27+
end
28+
end

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/RubyBasicObject.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,6 @@ public RubyClass makeMetaClass(RubyClass superClass) {
501501

502502
klass.setMetaClass(superClass.getRealClass().metaClass);
503503

504-
superClass.addSubclass(klass);
505-
506504
return klass;
507505
}
508506

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

Lines changed: 166 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@
4848
import static org.objectweb.asm.Opcodes.ACC_VARARGS;
4949

5050
import java.io.IOException;
51+
import java.lang.ref.WeakReference;
5152
import java.lang.reflect.Constructor;
5253
import java.lang.reflect.InvocationTargetException;
5354
import java.lang.reflect.Method;
5455
import java.lang.reflect.Modifier;
5556
import java.util.*;
57+
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
5658
import java.util.stream.Collectors;
5759

5860
import org.jruby.anno.JRubyClass;
@@ -93,7 +95,6 @@
9395
import org.jruby.util.Loader;
9496
import org.jruby.util.OneShotClassLoader;
9597
import org.jruby.util.StringSupport;
96-
import org.jruby.util.collections.ConcurrentWeakHashMap;
9798
import org.jruby.util.log.Logger;
9899
import org.jruby.util.log.LoggerFactory;
99100
import org.objectweb.asm.AnnotationVisitor;
@@ -110,6 +111,7 @@
110111
public class RubyClass extends RubyModule {
111112

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

114116
public static void createClassClass(Ruby runtime, RubyClass classClass) {
115117
classClass.setClassIndex(ClassIndex.CLASS);
@@ -1008,20 +1010,11 @@ public IRubyObject initialize_copy(IRubyObject original) {
10081010
return this;
10091011
}
10101012

1011-
protected void setModuleSuperClass(RubyClass superClass) {
1012-
// remove us from old superclass's child classes
1013-
if (this.superClass != null) this.superClass.removeSubclass(this);
1014-
// add us to new superclass's child classes
1015-
superClass.addSubclass(this);
1016-
// update superclass reference
1017-
setSuperClass(superClass);
1018-
}
1019-
10201013
@JRubyMethod
10211014
public IRubyObject subclasses(ThreadContext context) {
1022-
RubyArray<RubyClass> subs = RubyArray.newArray(context.runtime);
1023-
1024-
concreteSubclasses(subs);
1015+
RubyArray<RubyClass> subs = newConcreteSubclassesArray(context);
1016+
int clearedCount = concreteSubclasses(subs);
1017+
finishConcreteSubclasses(subs, clearedCount);
10251018

10261019
return subs;
10271020
}
@@ -1058,44 +1051,127 @@ public final Collection<RubyClass> subclasses() {
10581051
}
10591052

10601053
public Collection<RubyClass> subclasses(boolean includeDescendants) {
1061-
Map<RubyClass, Object> subclasses = this.subclasses;
1062-
if (subclasses != null) {
1063-
Collection<RubyClass> mine = new ArrayList<>();
1064-
subclassesInner(mine, includeDescendants);
1054+
Collection<RubyClass> mine = newSubclassesList(includeDescendants);
1055+
int clearedCount = subclassesInner(mine, includeDescendants);
1056+
finishSubclasses(mine, clearedCount, includeDescendants);
1057+
1058+
return mine;
1059+
}
1060+
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+
}
1070+
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;
10651077

1066-
return mine;
1078+
if (klass == null) {
1079+
clearedCount++;
1080+
continue;
1081+
}
1082+
1083+
processSubclass(mine, includeDescendants, klass);
10671084
}
1068-
return Collections.EMPTY_LIST;
1085+
return clearedCount;
10691086
}
10701087

1071-
private void subclassesInner(Collection<RubyClass> mine, boolean includeDescendants) {
1072-
Map<RubyClass, Object> subclasses = this.subclasses;
1073-
if (subclasses != null) {
1074-
Set<RubyClass> keys = subclasses.keySet();
1075-
mine.addAll(keys);
1076-
if (includeDescendants) {
1077-
for (RubyClass klass: keys) {
1078-
klass.subclassesInner(mine, includeDescendants);
1079-
}
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;
1100+
}
1101+
cleanSubclasses(newSize, clearedCount);
1102+
}
1103+
1104+
private int concreteSubclasses(RubyArray<RubyClass> subs) {
1105+
SubclassNode subclassNode = this.subclassNode;
1106+
int clearedCount = 0;
1107+
while (subclassNode != null) {
1108+
RubyClass klass = subclassNode.ref.get();
1109+
subclassNode = subclassNode.next;
1110+
1111+
if (klass == null) {
1112+
clearedCount++;
1113+
continue;
10801114
}
1115+
1116+
processConcreteSubclass(subs, klass);
10811117
}
1118+
return clearedCount;
10821119
}
10831120

1084-
private void concreteSubclasses(Collection<RubyClass> subs) {
1085-
Map<RubyClass, Object> subclasses = this.subclasses;
1086-
if (subclasses != null) {
1087-
Set<RubyClass> keys = subclasses.keySet();
1088-
for (RubyClass klass: keys) {
1089-
if (klass.isSingleton()) continue;
1090-
if (klass.isIncluded() || klass.isPrepended()) {
1091-
klass.concreteSubclasses(subs);
1092-
continue;
1093-
}
1094-
subs.add(klass);
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);
10951127
}
10961128
}
10971129
}
10981130

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+
}
1146+
}
1147+
}
1148+
1149+
private static SubclassNode rebuildSubclasses(SubclassNode subclassNode) {
1150+
SubclassNode newTop = null;
1151+
while (subclassNode != null) {
1152+
WeakReference<RubyClass> ref = subclassNode.ref;
1153+
RubyClass klass = ref.get();
1154+
subclassNode = subclassNode.next;
1155+
if (klass == null) continue;
1156+
newTop = new SubclassNode(ref, newTop);
1157+
}
1158+
return newTop;
1159+
}
1160+
1161+
// TODO: make into a Record
1162+
static class SubclassNode {
1163+
final SubclassNode next;
1164+
final WeakReference<RubyClass> ref;
1165+
SubclassNode(RubyClass klass, SubclassNode next) {
1166+
ref = new WeakReference<>(klass);
1167+
this.next = next;
1168+
}
1169+
SubclassNode(WeakReference<RubyClass> ref, SubclassNode next) {
1170+
this.ref = ref;
1171+
this.next = next;
1172+
}
1173+
}
1174+
10991175
/**
11001176
* Add a new subclass to the weak set of subclasses.
11011177
*
@@ -1106,18 +1182,12 @@ private void concreteSubclasses(Collection<RubyClass> subs) {
11061182
* @param subclass The subclass to add
11071183
*/
11081184
public void addSubclass(RubyClass subclass) {
1109-
Map<RubyClass, Object> subclasses = this.subclasses;
1110-
if (subclasses == null) {
1111-
// check again
1112-
synchronized (this) {
1113-
subclasses = this.subclasses;
1114-
if (subclasses == null) {
1115-
this.subclasses = subclasses = new ConcurrentWeakHashMap<>(4, 0.75f, 1);
1116-
}
1117-
}
1185+
SubclassNode subclassNode = this.subclassNode;
1186+
SubclassNode newNode = new SubclassNode(subclass, subclassNode);
1187+
while (!SUBCLASS_UPDATER.compareAndSet(this, subclassNode, newNode)) {
1188+
subclassNode = this.subclassNode;
1189+
newNode = new SubclassNode(subclass, subclassNode);
11181190
}
1119-
1120-
subclasses.put(subclass, NEVER);
11211191
}
11221192

11231193
/**
@@ -1126,10 +1196,16 @@ public void addSubclass(RubyClass subclass) {
11261196
* @param subclass The subclass to remove
11271197
*/
11281198
public void removeSubclass(RubyClass subclass) {
1129-
Map<RubyClass, Object> subclasses = this.subclasses;
1130-
if (subclasses == null) return;
1131-
1132-
subclasses.remove(subclass);
1199+
SubclassNode subclassNode = this.subclassNode;
1200+
while (subclassNode != null) {
1201+
WeakReference<RubyClass> ref = subclassNode.ref;
1202+
RubyClass klass = ref.get();
1203+
if (klass == subclass) {
1204+
ref.clear();
1205+
return;
1206+
}
1207+
subclassNode = subclassNode.next;
1208+
}
11331209
}
11341210

11351211
/**
@@ -1139,20 +1215,25 @@ public void removeSubclass(RubyClass subclass) {
11391215
* @param newSubclass The subclass to replace it with
11401216
*/
11411217
public void replaceSubclass(RubyClass subclass, RubyClass newSubclass) {
1142-
Map<RubyClass, Object> subclasses = this.subclasses;
1143-
if (subclasses == null) return;
1144-
1145-
subclasses.remove(subclass);
1146-
subclasses.put(newSubclass, NEVER);
1218+
removeSubclass(subclass);
1219+
addSubclass(newSubclass);
11471220
}
11481221

1222+
/**
1223+
* make this class and all subclasses sync
1224+
*/
11491225
@Override
11501226
public void becomeSynchronized() {
1151-
// make this class and all subclasses sync
11521227
super.becomeSynchronized();
1153-
Map<RubyClass, Object> subclasses = this.subclasses;
1154-
if (subclasses != null) {
1155-
for (RubyClass subclass : subclasses.keySet()) subclass.becomeSynchronized();
1228+
1229+
SubclassNode subclassNode = this.subclassNode;
1230+
while (subclassNode != null) {
1231+
WeakReference<RubyClass> ref = subclassNode.ref;
1232+
RubyClass klass = ref.get();
1233+
if (klass != null) {
1234+
klass.becomeSynchronized();
1235+
}
1236+
subclassNode = subclassNode.next;
11561237
}
11571238
}
11581239

@@ -1172,9 +1253,14 @@ public void becomeSynchronized() {
11721253
public void invalidateCacheDescendants() {
11731254
super.invalidateCacheDescendants();
11741255

1175-
Map<RubyClass, Object> subclasses = this.subclasses;
1176-
if (subclasses != null) {
1177-
for (RubyClass subclass : subclasses.keySet()) subclass.invalidateCacheDescendants();
1256+
SubclassNode subclassNode = this.subclassNode;
1257+
while (subclassNode != null) {
1258+
WeakReference<RubyClass> ref = subclassNode.ref;
1259+
RubyClass klass = ref.get();
1260+
if (klass != null) {
1261+
klass.invalidateCacheDescendants();
1262+
}
1263+
subclassNode = subclassNode.next;
11781264
}
11791265
}
11801266

@@ -1185,12 +1271,15 @@ public void addInvalidatorsAndFlush(List<Invalidator> invalidators) {
11851271
// if we're not at boot time, don't bother fully clearing caches
11861272
if (!runtime.isBootingCore()) cachedMethods.clear();
11871273

1188-
Map<RubyClass, Object> subclasses = this.subclasses;
1189-
// no subclasses, don't bother with lock and iteration
1190-
if (subclasses == null || subclasses.isEmpty()) return;
1191-
1192-
// cascade into subclasses
1193-
for (RubyClass subclass : subclasses.keySet()) subclass.addInvalidatorsAndFlush(invalidators);
1274+
SubclassNode subclassNode = this.subclassNode;
1275+
while (subclassNode != null) {
1276+
WeakReference<RubyClass> ref = subclassNode.ref;
1277+
RubyClass klass = ref.get();
1278+
if (klass != null) {
1279+
klass.addInvalidatorsAndFlush(invalidators);
1280+
}
1281+
subclassNode = subclassNode.next;
1282+
}
11941283
}
11951284

11961285
public final Ruby getClassRuntime() {
@@ -3085,7 +3174,11 @@ public IRubyObject invokeFrom(ThreadContext context, CallType callType, IRubyObj
30853174
protected final Ruby runtime;
30863175
private ObjectAllocator allocator; // the default allocator
30873176
protected ObjectMarshal marshal;
3088-
private volatile Map<RubyClass, Object> subclasses;
3177+
private volatile SubclassNode subclassNode;
3178+
private static final AtomicReferenceFieldUpdater SUBCLASS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(RubyClass.class, SubclassNode.class, "subclassNode");
3179+
private int concreteSubclassesEstimate = 4;
3180+
private int allDescendantsEstimate = 4;
3181+
private int allSubclassesEstimate = 4;
30893182
public static final int CS_IDX_INITIALIZE = 0;
30903183
public enum CS_NAMES {
30913184
INITIALIZE("initialize");

0 commit comments

Comments
 (0)