Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Implement class hierarchy method caching #387

Closed
wants to merge 13 commits into from

4 participants

Charlie Somerville Magnus Holm Nami-Doc Koichi Sasada
Charlie Somerville
Collaborator

I've implemented class hierarchy method caching against the current trunk.

I've skipped a bunch of commits relating to constant caching in this pull request that were in the klasscache-trunk branch, because they were causing hard to resolve merge conflicts.

Once this patch set is reviewed and I have merged it into SVN trunk, I'll work on implementing better constant caching again.

cc @ko1

Magnus Holm

How will this affect #extend? Will #extend still clear the global method cache?

Charlie Somerville
Collaborator

@judofyr No. extend calls rb_include_module on the singleton class which takes care of hierarchical invalidation.

Because singleton classes have no descendants, #extend is now quite cheap!

Nami-Doc

Whoaw. Great work, as always.

include/ruby/ruby.h
@@ -785,12 +785,18 @@ struct RObject {
785 785
 /** @internal */
786 786
 typedef struct rb_classext_struct rb_classext_t;
787 787
 
  788
+/**
1
Koichi Sasada
ko1 added a note September 02, 2013

This comment should be at internal.h.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
include/ruby/intern.h
@@ -374,7 +374,7 @@
374 374
 void rb_define_alloc_func(VALUE, rb_alloc_func_t);
375 375
 void rb_undef_alloc_func(VALUE);
376 376
 rb_alloc_func_t rb_get_alloc_func(VALUE);
377  
-void rb_clear_cache(void);
  377
+void rb_clear_cache_globally(void);
1
Koichi Sasada
ko1 added a note September 02, 2013

should not change global function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
include/ruby/st.h
@@ -131,6 +131,56 @@ struct st_table {
131 131
 st_index_t st_hash_start(st_index_t h);
132 132
 #define st_hash_start(h) ((st_index_t)(h))
133 133
 
  134
+#if HAVE_UINT32_T
1
Koichi Sasada
ko1 added a note September 02, 2013

should be on sp_ar.h on top of srcdir.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Koichi Sasada ko1 commented on the diff September 02, 2013
@@ -11,6 +11,8 @@
11 11
 #ifndef METHOD_H
12 12
 #define METHOD_H
13 13
 
  14
+#include "internal.h"
2
Koichi Sasada
ko1 added a note September 02, 2013

why that?

Charlie Somerville Collaborator

for method_cache_entry_t

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
variable.c
@@ -1939,7 +1939,10 @@ struct autoload_const_set_args {
1939 1939
 		      rb_class_name(mod), QUOTE_ID(id));
1940 1940
     }
1941 1941
 
1942  
-    rb_vm_change_state();
  1942
+    rb_clear_cache_by_class(mod);
  1943
+    /* KLASSCACHE-TODO: constant lookup is keyed on vm_state_version only, so
  1944
+       we need to do a global invalidation here... */
  1945
+    rb_clear_cache_globally();
2
Koichi Sasada
ko1 added a note September 02, 2013

rb_clear_cache_globally() should be called in rb_clear_cache_by_class() (and similar functions).
I recoomend that ver up vm_version simply.

Charlie Somerville Collaborator

I will just remove the call to rb_clear_cache_by_class(mod); here for now. I will remove the global invalidation when I add back name-based constant caching.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Koichi Sasada ko1 commented on the diff September 02, 2013
@@ -84,6 +87,12 @@
84 87
 #define BUFSIZE 0x100
85 88
 #define PROCDEBUG 0
86 89
 
  90
+vm_state_version_t
1
Koichi Sasada
ko1 added a note September 02, 2013

rb_next_seq() is too simple name.
It should be more descriptible name,
Maybe, basically, `seq' is too simple.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Charlie Somerville
Collaborator

@ko1 I have addressed your feedback

Koichi Sasada ko1 commented on the diff September 02, 2013
((6 lines not shown))
  1257
+	    if (BUILTIN_TYPE(obj) == T_MODULE) {
  1258
+		rb_class_detach_module_subclasses(obj);
  1259
+	    } else {
  1260
+		rb_class_detach_subclasses(obj);
  1261
+	    }
  1262
+	    RCLASS_EXT(obj)->subclasses = NULL;
  1263
+	}
  1264
+	if (RCLASS_EXT(obj)->mc_tbl) {
  1265
+	    rb_free_mc_table(RCLASS_EXT(obj)->mc_tbl);
  1266
+	    RCLASS_EXT(obj)->mc_tbl = NULL;
  1267
+	}
  1268
+	rb_class_remove_from_module_subclasses(obj);
  1269
+	rb_class_remove_from_super_subclasses(obj);
  1270
+	if (RANY(obj)->as.klass.ptr)
  1271
+	    xfree(RANY(obj)->as.klass.ptr);
  1272
+	RANY(obj)->as.klass.ptr = NULL;
1
Koichi Sasada
ko1 added a note September 02, 2013

don't need to clear RANY(obj)->as.klass.ptr .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Koichi Sasada ko1 commented on the diff September 02, 2013
((10 lines not shown))
1294 1330
 	xfree(RANY(obj)->as.klass.ptr);
  1331
+	RANY(obj)->as.klass.ptr = NULL;
1
Koichi Sasada
ko1 added a note September 02, 2013

ditto.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Koichi Sasada

I read whole of patch and it seems good.(no problem)
Thank you for your great effort, and yoru patient.

I and Nobu discussed about this patch and we consider 32bit restriction of sp_ar_index_t is not desireble for future.
Options:
(1) use only st and make another patch to introduce sp_ar.[ch] with exact benchmark
this option make this patch simple. I prefer this option.
After commit without sp_ar.[ch], then discuss with benchmark result.
(2) Use st_data_t as sp_ar_index_t.
Easy way to fix this patch.

Trivial points:
(a) memory consumption. Do you have measurement environmen?
If you don't have, I'll check them on my environment.

(b) function and type names
Can I fix after your commits after discussion with you?
`seq' is too simple for me.

Charlie Somerville
Collaborator

(1) use only st and make another patch to introduce sp_ar.[ch] with exact benchmark
this option make this patch simple. I prefer this option.
After commit without sp_ar.[ch], then discuss with benchmark result.

I agree this is the best option. I will remove sp_ar from this patch and then we can discuss adding it back later.

(a) memory consumption. Do you have measurement environmen?
If you don't have, I'll check them on my environment.

I don't, sorry.

(b) function and type names
Can I fix after your commits after discussion with you?
`seq' is too simple for me.

Do you mean that you will fix naming after I commit this to trunk? I'm ok with this.

Koichi Sasada
(1) use only st and make another patch to introduce sp_ar.[ch] with exact benchmark
this option make this patch simple. I prefer this option.
After commit without sp_ar.[ch], then discuss with benchmark result.

I agree this is the best option. I will remove sp_ar from this patch and then we can discuss adding it back later.

Thank you for additional effort.

(a) memory consumption. Do you have measurement environmen?
If you don't have, I'll check them on my environment.

I don't, sorry.

Ok. I'll check it.

(b) function and type names
Can I fix after your commits after discussion with you?
`seq' is too simple for me.

Do you mean that you will fix naming after I commit this to trunk? I'm ok with this.

Yes. It is trivial problem.
I want to unify seq and state_version.
(i) vm_state_version -> vm_seq_number
(ii) seq -> state_version
(iii) other good name

Koichi Sasada

This is other comments for future:

(1) Can we unify method table and method caching table?
If we can, it reduces memory consumption.

(2) do you want to replace st.[ch] with sp_ar.[ch] for ID key tables?
I can agree with benchmark results.

Magnus Holm
Koichi Sasada

Memory consumption for test-all.
http://www.atdot.net/fp_store/f.3ztjsm/file.copipa-temp-image.png
not so big difference (but on not practical usecase)

Charlie Somerville
Collaborator

@judofyr Yep, the #bar call site will miss often in your example.

Charlie Somerville
Collaborator

@ko1 I have removed sparse array, so I hope I can merge this into trunk today if it looks good to you.

Charlie Somerville charliesome referenced this pull request from a commit in charliesome/ruby September 04, 2013
Charlie Somerville * class.c, compile.c, eval.c, gc.h, insns.def, internal.h, method.h,
  variable.c, vm.c, vm_core.c, vm_insnhelper.c, vm_insnhelper.h,
  vm_method.c: Implement class hierarchy method cache invalidation.

  [ruby-core:55053] [Feature #8426] [GH-387]
d88d250
Charlie Somerville charliesome referenced this pull request from a commit September 04, 2013
Charlie Somerville * class.c, compile.c, eval.c, gc.h, insns.def, internal.h, method.h,
  variable.c, vm.c, vm_core.c, vm_insnhelper.c, vm_insnhelper.h,
  vm_method.c: Implement class hierarchy method cache invalidation.

  [ruby-core:55053] [Feature #8426] [GH-387]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@42822 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2f522b9
Charlie Somerville charliesome closed this September 03, 2013
Charlie Somerville charliesome referenced this pull request from a commit September 25, 2013
Commit has since been removed from the repository and is no longer available.
Charlie Somerville charliesome referenced this pull request from a commit in github/ruby September 25, 2013
Charlie Somerville * class.c, compile.c, eval.c, gc.h, insns.def, internal.h, method.h,
  variable.c, vm.c, vm_core.c, vm_insnhelper.c, vm_insnhelper.h,
  vm_method.c: Implement class hierarchy method cache invalidation.

  [ruby-core:55053] [Feature #8426] [GH-387]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@42822 b2dd03c8-39d4-4d8f-98ff-823fe69b080e

Conflicts:
	ChangeLog
	class.c
	eval.c
	internal.h
	variable.c
	vm_insnhelper.c
756d555
Aaron Patterson tenderlove referenced this pull request from a commit in tenderlove/ruby September 04, 2013
Charlie Somerville * class.c, compile.c, eval.c, gc.h, insns.def, internal.h, method.h,
  variable.c, vm.c, vm_core.c, vm_insnhelper.c, vm_insnhelper.h,
  vm_method.c: Implement class hierarchy method cache invalidation.

  [ruby-core:55053] [Feature #8426] [GH-387]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@42822 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
23eef99
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
135  class.c
@@ -34,6 +34,109 @@
34 34
 extern st_table *rb_class_tbl;
35 35
 #define id_attached id__attached__
36 36
 
  37
+void
  38
+rb_class_subclass_add(VALUE super, VALUE klass)
  39
+{
  40
+    rb_subclass_entry_t *entry, *head;
  41
+
  42
+    if (super && super != Qundef) {
  43
+	entry = malloc(sizeof(*entry));
  44
+	entry->klass = klass;
  45
+	entry->next = NULL;
  46
+
  47
+	head = RCLASS_EXT(super)->subclasses;
  48
+	if (head) {
  49
+	    entry->next = head;
  50
+	    RCLASS_EXT(head->klass)->parent_subclasses = &entry->next;
  51
+	}
  52
+
  53
+	RCLASS_EXT(super)->subclasses = entry;
  54
+	RCLASS_EXT(klass)->parent_subclasses = &RCLASS_EXT(super)->subclasses;
  55
+    }
  56
+}
  57
+
  58
+static void
  59
+rb_module_add_to_subclasses_list(VALUE module, VALUE iclass)
  60
+{
  61
+    rb_subclass_entry_t *entry, *head;
  62
+
  63
+    entry = malloc(sizeof(*entry));
  64
+    entry->klass = iclass;
  65
+    entry->next = NULL;
  66
+
  67
+    head = RCLASS_EXT(module)->subclasses;
  68
+    if (head) {
  69
+	entry->next = head;
  70
+	RCLASS_EXT(head->klass)->module_subclasses = &entry->next;
  71
+    }
  72
+
  73
+    RCLASS_EXT(module)->subclasses = entry;
  74
+    RCLASS_EXT(iclass)->module_subclasses = &RCLASS_EXT(module)->subclasses;
  75
+}
  76
+
  77
+void
  78
+rb_class_remove_from_super_subclasses(VALUE klass)
  79
+{
  80
+    rb_subclass_entry_t *entry;
  81
+
  82
+    if (RCLASS_EXT(klass)->parent_subclasses) {
  83
+	entry = *RCLASS_EXT(klass)->parent_subclasses;
  84
+
  85
+	*RCLASS_EXT(klass)->parent_subclasses = entry->next;
  86
+	if (entry->next) {
  87
+	    RCLASS_EXT(entry->next->klass)->parent_subclasses = RCLASS_EXT(klass)->parent_subclasses;
  88
+	}
  89
+	free(entry);
  90
+    }
  91
+
  92
+    RCLASS_EXT(klass)->parent_subclasses = NULL;
  93
+}
  94
+
  95
+void
  96
+rb_class_remove_from_module_subclasses(VALUE klass)
  97
+{
  98
+    rb_subclass_entry_t *entry;
  99
+
  100
+    if (RCLASS_EXT(klass)->module_subclasses) {
  101
+	entry = *RCLASS_EXT(klass)->module_subclasses;
  102
+	*RCLASS_EXT(klass)->module_subclasses = entry->next;
  103
+
  104
+	if (entry->next) {
  105
+	    RCLASS_EXT(entry->next->klass)->module_subclasses = RCLASS_EXT(klass)->module_subclasses;
  106
+	}
  107
+
  108
+	free(entry);
  109
+    }
  110
+
  111
+    RCLASS_EXT(klass)->module_subclasses = NULL;
  112
+}
  113
+
  114
+void
  115
+rb_class_foreach_subclass(VALUE klass, void(*f)(VALUE))
  116
+{
  117
+    rb_subclass_entry_t *cur = RCLASS_EXT(klass)->subclasses;
  118
+
  119
+    /* do not be tempted to simplify this loop into a for loop, the order of
  120
+       operations is important here if `f` modifies the linked list */
  121
+    while (cur) {
  122
+	VALUE curklass = cur->klass;
  123
+	cur = cur->next;
  124
+	f(curklass);
  125
+    }
  126
+}
  127
+
  128
+void
  129
+rb_class_detach_subclasses(VALUE klass)
  130
+{
  131
+    rb_class_foreach_subclass(klass, rb_class_remove_from_super_subclasses);
  132
+}
  133
+
  134
+void
  135
+rb_class_detach_module_subclasses(VALUE klass)
  136
+{
  137
+    rb_class_foreach_subclass(klass, rb_class_remove_from_module_subclasses);
  138
+}
  139
+
37 140
 /**
38 141
  * Allocates a struct RClass for a new class.
39 142
  *
@@ -57,6 +160,13 @@ class_alloc(VALUE flags, VALUE klass)
57 160
     RCLASS_SET_SUPER((VALUE)obj, 0);
58 161
     RCLASS_ORIGIN(obj) = (VALUE)obj;
59 162
     RCLASS_IV_INDEX_TBL(obj) = 0;
  163
+
  164
+    RCLASS_EXT(obj)->subclasses = NULL;
  165
+    RCLASS_EXT(obj)->parent_subclasses = NULL;
  166
+    RCLASS_EXT(obj)->module_subclasses = NULL;
  167
+    RCLASS_EXT(obj)->seq = rb_next_class_sequence();
  168
+    RCLASS_EXT(obj)->mc_tbl = NULL;
  169
+
60 170
     RCLASS_REFINED_CLASS(obj) = Qnil;
61 171
     RCLASS_EXT(obj)->allocator = 0;
62 172
     return (VALUE)obj;
@@ -723,7 +833,6 @@ rb_include_module(VALUE klass, VALUE module)
723 833
     changed = include_modules_at(klass, RCLASS_ORIGIN(klass), module);
724 834
     if (changed < 0)
725 835
 	rb_raise(rb_eArgError, "cyclic include detected");
726  
-    if (changed) rb_clear_cache();
727 836
 }
728 837
 
729 838
 static int
@@ -736,8 +845,8 @@ add_refined_method_entry_i(st_data_t key, st_data_t value, st_data_t data)
736 845
 static int
737 846
 include_modules_at(const VALUE klass, VALUE c, VALUE module)
738 847
 {
739  
-    VALUE p;
740  
-    int changed = 0;
  848
+    VALUE p, iclass;
  849
+    int method_changed = 0, constant_changed = 0;
741 850
     const st_table *const klass_m_tbl = RCLASS_M_TBL(RCLASS_ORIGIN(klass));
742 851
 
743 852
     while (module) {
@@ -763,7 +872,15 @@ include_modules_at(const VALUE klass, VALUE c, VALUE module)
763 872
 		break;
764 873
 	    }
765 874
 	}
766  
-	c = RCLASS_SET_SUPER(c, rb_include_class_new(module, RCLASS_SUPER(c)));
  875
+	iclass = rb_include_class_new(module, RCLASS_SUPER(c));
  876
+	c = RCLASS_SET_SUPER(c, iclass);
  877
+
  878
+	if (BUILTIN_TYPE(module) == T_ICLASS) {
  879
+	    rb_module_add_to_subclasses_list(RBASIC(module)->klass, iclass);
  880
+	} else {
  881
+	    rb_module_add_to_subclasses_list(module, iclass);
  882
+	}
  883
+
767 884
 	if (FL_TEST(klass, RMODULE_IS_REFINEMENT)) {
768 885
 	    VALUE refined_class =
769 886
 		rb_refinement_module_get_refined_class(klass);
@@ -773,14 +890,17 @@ include_modules_at(const VALUE klass, VALUE c, VALUE module)
773 890
 	    FL_SET(c, RMODULE_INCLUDED_INTO_REFINEMENT);
774 891
 	}
775 892
 	if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries)
776  
-	    changed = 1;
  893
+	    method_changed = 1;
777 894
 	if (RMODULE_CONST_TBL(module) && RMODULE_CONST_TBL(module)->num_entries)
778  
-	    changed = 1;
  895
+	    constant_changed = 1;
779 896
       skip:
780 897
 	module = RCLASS_SUPER(module);
781 898
     }
782 899
 
783  
-    return changed;
  900
+    if (method_changed) rb_clear_cache_by_class(klass);
  901
+    if (constant_changed) rb_clear_cache();
  902
+
  903
+    return method_changed;
784 904
 }
785 905
 
786 906
 static int
@@ -839,7 +959,6 @@ rb_prepend_module(VALUE klass, VALUE module)
839 959
     if (changed < 0)
840 960
 	rb_raise(rb_eArgError, "cyclic prepend detected");
841 961
     if (changed) {
842  
-	rb_clear_cache();
843 962
 	rb_vm_check_redefinition_by_prepend(klass);
844 963
     }
845 964
 }
1  compile.c
@@ -962,6 +962,7 @@ new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned long flag)
962 962
 	}
963 963
     }
964 964
     ci->vmstat = 0;
  965
+    ci->seq = 0;
965 966
     ci->blockptr = 0;
966 967
     ci->recv = Qundef;
967 968
     ci->call = 0; /* TODO: should set default function? */
4  eval.c
@@ -1262,7 +1262,7 @@ mod_using(VALUE self, VALUE module)
1262 1262
     }
1263 1263
     Check_Type(module, T_MODULE);
1264 1264
     rb_using_module(cref, module);
1265  
-    rb_clear_cache();
  1265
+    rb_clear_cache_by_class(rb_cObject);
1266 1266
     return self;
1267 1267
 }
1268 1268
 
@@ -1398,7 +1398,7 @@ top_using(VALUE self, VALUE module)
1398 1398
     }
1399 1399
     Check_Type(module, T_MODULE);
1400 1400
     rb_using_module(cref, module);
1401  
-    rb_clear_cache();
  1401
+    rb_clear_cache_by_class(rb_cObject);
1402 1402
     return self;
1403 1403
 }
1404 1404
 
40  gc.c
@@ -1122,6 +1122,20 @@ rb_free_m_table(st_table *tbl)
1122 1122
 }
1123 1123
 
1124 1124
 static int
  1125
+free_method_cache_entry_i(ID key, method_cache_entry_t *entry, st_data_t data)
  1126
+{
  1127
+    free(entry);
  1128
+    return ST_CONTINUE;
  1129
+}
  1130
+
  1131
+void
  1132
+rb_free_mc_table(st_table *tbl)
  1133
+{
  1134
+    st_foreach(tbl, free_method_cache_entry_i, 0);
  1135
+    st_free_table(tbl);
  1136
+}
  1137
+
  1138
+static int
1125 1139
 free_const_entry_i(ID key, rb_const_entry_t *ce, st_data_t data)
1126 1140
 {
1127 1141
     xfree(ce);
@@ -1226,7 +1240,6 @@ obj_free(rb_objspace_t *objspace, VALUE obj)
1226 1240
 	break;
1227 1241
       case T_MODULE:
1228 1242
       case T_CLASS:
1229  
-	rb_clear_cache_by_class((VALUE)obj);
1230 1243
         if (RCLASS_M_TBL(obj)) {
1231 1244
             rb_free_m_table(RCLASS_M_TBL(obj));
1232 1245
         }
@@ -1239,7 +1252,23 @@ obj_free(rb_objspace_t *objspace, VALUE obj)
1239 1252
 	if (RCLASS_IV_INDEX_TBL(obj)) {
1240 1253
 	    st_free_table(RCLASS_IV_INDEX_TBL(obj));
1241 1254
 	}
1242  
-        xfree(RANY(obj)->as.klass.ptr);
  1255
+	if (RCLASS_EXT(obj)->subclasses) {
  1256
+	    if (BUILTIN_TYPE(obj) == T_MODULE) {
  1257
+		rb_class_detach_module_subclasses(obj);
  1258
+	    } else {
  1259
+		rb_class_detach_subclasses(obj);
  1260
+	    }
  1261
+	    RCLASS_EXT(obj)->subclasses = NULL;
  1262
+	}
  1263
+	if (RCLASS_EXT(obj)->mc_tbl) {
  1264
+	    rb_free_mc_table(RCLASS_EXT(obj)->mc_tbl);
  1265
+	    RCLASS_EXT(obj)->mc_tbl = NULL;
  1266
+	}
  1267
+	rb_class_remove_from_module_subclasses(obj);
  1268
+	rb_class_remove_from_super_subclasses(obj);
  1269
+	if (RANY(obj)->as.klass.ptr)
  1270
+	    xfree(RANY(obj)->as.klass.ptr);
  1271
+	RANY(obj)->as.klass.ptr = NULL;
1243 1272
 	break;
1244 1273
       case T_STRING:
1245 1274
 	rb_str_free(obj);
@@ -1291,7 +1320,14 @@ obj_free(rb_objspace_t *objspace, VALUE obj)
1291 1320
 	break;
1292 1321
       case T_ICLASS:
1293 1322
 	/* iClass shares table with the module */
  1323
+	if (RCLASS_EXT(obj)->subclasses) {
  1324
+	    rb_class_detach_subclasses(obj);
  1325
+	    RCLASS_EXT(obj)->subclasses = NULL;
  1326
+	}
  1327
+	rb_class_remove_from_module_subclasses(obj);
  1328
+	rb_class_remove_from_super_subclasses(obj);
1294 1329
 	xfree(RANY(obj)->as.klass.ptr);
  1330
+	RANY(obj)->as.klass.ptr = NULL;
1295 1331
 	break;
1296 1332
 
1297 1333
       case T_FLOAT:
1  include/ruby/ruby.h
@@ -791,6 +791,7 @@ struct RClass {
791 791
     struct st_table *m_tbl;
792 792
     struct st_table *iv_index_tbl;
793 793
 };
  794
+
794 795
 #define RCLASS_SUPER(c) rb_class_get_superclass(c)
795 796
 #define RMODULE_IV_TBL(m) RCLASS_IV_TBL(m)
796 797
 #define RMODULE_CONST_TBL(m) RCLASS_CONST_TBL(m)
4  insns.def
@@ -218,7 +218,7 @@ setconstant
218 218
 {
219 219
     vm_check_if_namespace(cbase);
220 220
     rb_const_set(cbase, id, val);
221  
-    INC_VM_STATE_VERSION();
  221
+    rb_clear_cache_by_class(cbase);
222 222
 }
223 223
 
224 224
 /**
@@ -975,7 +975,7 @@ defineclass
975 975
 		  class_iseq->local_size, 0, class_iseq->stack_max);
976 976
     RESTORE_REGS();
977 977
 
978  
-    INC_VM_STATE_VERSION();
  978
+    rb_clear_cache_by_class(klass);
979 979
     NEXT_INSN();
980 980
 }
981 981
 
49  internal.h
@@ -110,15 +110,53 @@ struct rb_deprecated_classext_struct {
110 110
     char conflict[sizeof(VALUE) * 3];
111 111
 };
112 112
 
  113
+struct rb_subclass_entry;
  114
+typedef struct rb_subclass_entry rb_subclass_entry_t;
  115
+
  116
+struct rb_subclass_entry {
  117
+    VALUE klass;
  118
+    rb_subclass_entry_t *next;
  119
+};
  120
+
  121
+#if HAVE_UINT64_T
  122
+    typedef uint64_t vm_state_version_t;
  123
+#else
  124
+    typedef unsigned long long vm_state_version_t;
  125
+#endif
  126
+
  127
+struct rb_method_entry_struct;
  128
+
  129
+typedef struct method_cache_entry {
  130
+    vm_state_version_t vm_state;
  131
+    vm_state_version_t seq;
  132
+    ID mid;
  133
+    VALUE defined_class;
  134
+    struct rb_method_entry_struct *me;
  135
+} method_cache_entry_t;
  136
+
113 137
 struct rb_classext_struct {
114 138
     VALUE super;
115 139
     struct st_table *iv_tbl;
116 140
     struct st_table *const_tbl;
  141
+    struct st_table *mc_tbl;
  142
+    rb_subclass_entry_t *subclasses;
  143
+    rb_subclass_entry_t **parent_subclasses;
  144
+    /**
  145
+     * In the case that this is an `ICLASS`, `module_subclasses` points to the link
  146
+     * in the module's `subclasses` list that indicates that the klass has been
  147
+     * included. Hopefully that makes sense.
  148
+     */
  149
+    rb_subclass_entry_t **module_subclasses;
  150
+    vm_state_version_t seq;
117 151
     VALUE origin;
118 152
     VALUE refined_class;
119 153
     rb_alloc_func_t allocator;
120 154
 };
121 155
 
  156
+/* class.c */
  157
+void rb_class_subclass_add(VALUE super, VALUE klass);
  158
+void rb_class_remove_from_super_subclasses(VALUE);
  159
+
122 160
 #define RCLASS_EXT(c) (RCLASS(c)->ptr)
123 161
 #define RCLASS_IV_TBL(c) (RCLASS_EXT(c)->iv_tbl)
124 162
 #define RCLASS_CONST_TBL(c) (RCLASS_EXT(c)->const_tbl)
@@ -137,6 +175,10 @@ RCLASS_SUPER(VALUE klass)
137 175
 static inline VALUE
138 176
 RCLASS_SET_SUPER(VALUE klass, VALUE super)
139 177
 {
  178
+    if (super) {
  179
+	rb_class_remove_from_super_subclasses(klass);
  180
+	rb_class_subclass_add(super, klass);
  181
+    }
140 182
     OBJ_WRITE(klass, &RCLASS_EXT(klass)->super, super);
141 183
     return super;
142 184
 }
@@ -156,6 +198,10 @@ VALUE rb_integer_float_cmp(VALUE x, VALUE y);
156 198
 VALUE rb_integer_float_eq(VALUE x, VALUE y);
157 199
 
158 200
 /* class.c */
  201
+void rb_class_foreach_subclass(VALUE klass, void(*f)(VALUE));
  202
+void rb_class_detach_subclasses(VALUE);
  203
+void rb_class_detach_module_subclasses(VALUE);
  204
+void rb_class_remove_from_module_subclasses(VALUE);
159 205
 VALUE rb_obj_methods(int argc, VALUE *argv, VALUE obj);
160 206
 VALUE rb_obj_protected_methods(int argc, VALUE *argv, VALUE obj);
161 207
 VALUE rb_obj_private_methods(int argc, VALUE *argv, VALUE obj);
@@ -462,6 +508,9 @@ void ruby_kill(rb_pid_t pid, int sig);
462 508
 /* thread_pthread.c, thread_win32.c */
463 509
 void Init_native_thread(void);
464 510
 
  511
+/* vm_insnhelper.h */
  512
+vm_state_version_t rb_next_class_sequence();
  513
+
465 514
 /* vm.c */
466 515
 VALUE rb_obj_is_thread(VALUE obj);
467 516
 void rb_vm_mark(void *ptr);
4  method.h
@@ -11,6 +11,8 @@
11 11
 #ifndef METHOD_H
12 12
 #define METHOD_H
13 13
 
  14
+#include "internal.h"
  15
+
14 16
 #ifndef END_OF_ENUMERATION
15 17
 # ifdef __GNUC__
16 18
 #   define END_OF_ENUMERATION(key)
@@ -120,7 +122,7 @@ rb_method_entry_t *rb_method_entry_with_refinements(VALUE klass, ID id,
120 122
 rb_method_entry_t *rb_method_entry_without_refinements(VALUE klass, ID id,
121 123
 						       VALUE *defined_class_ptr);
122 124
 
123  
-rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, ID id, VALUE *define_class_ptr);
  125
+rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, ID id, VALUE *define_class_ptr, method_cache_entry_t *ent);
124 126
 rb_method_entry_t *rb_method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *, rb_method_flag_t noex);
125 127
 
126 128
 int rb_method_entry_arity(const rb_method_entry_t *me);
21  variable.c
@@ -1939,7 +1939,7 @@ rb_const_remove(VALUE mod, ID id)
1939 1939
 		      rb_class_name(mod), QUOTE_ID(id));
1940 1940
     }
1941 1941
 
1942  
-    rb_vm_change_state();
  1942
+    rb_clear_cache();
1943 1943
 
1944 1944
     val = ((rb_const_entry_t*)v)->value;
1945 1945
     if (val == Qundef) {
@@ -2149,7 +2149,8 @@ rb_const_set(VALUE klass, ID id, VALUE val)
2149 2149
 		load = autoload_data(klass, id);
2150 2150
 		/* for autoloading thread, keep the defined value to autoloading storage */
2151 2151
 		if (load && (ele = check_autoload_data(load)) && (ele->thread == rb_thread_current())) {
2152  
-		    rb_vm_change_state();
  2152
+		    rb_clear_cache();
  2153
+
2153 2154
 		    ele->value = val; /* autoload_i is shady */
2154 2155
 		    return;
2155 2156
 		}
@@ -2172,7 +2173,8 @@ rb_const_set(VALUE klass, ID id, VALUE val)
2172 2173
 	}
2173 2174
     }
2174 2175
 
2175  
-    rb_vm_change_state();
  2176
+    rb_clear_cache();
  2177
+
2176 2178
 
2177 2179
     ce = ALLOC(rb_const_entry_t);
2178 2180
     MEMZERO(ce, rb_const_entry_t, 1);
@@ -2217,8 +2219,10 @@ set_const_visibility(VALUE mod, int argc, VALUE *argv, rb_const_flag_t flag)
2217 2219
 	VALUE val = argv[i];
2218 2220
 	id = rb_check_id(&val);
2219 2221
 	if (!id) {
2220  
-	    if (i > 0)
2221  
-		rb_clear_cache_by_class(mod);
  2222
+	    if (i > 0) {
  2223
+		rb_clear_cache();
  2224
+	    }
  2225
+
2222 2226
 	    rb_name_error_str(val, "constant %"PRIsVALUE"::%"PRIsVALUE" not defined",
2223 2227
 			      rb_class_name(mod), QUOTE(val));
2224 2228
 	}
@@ -2227,13 +2231,14 @@ set_const_visibility(VALUE mod, int argc, VALUE *argv, rb_const_flag_t flag)
2227 2231
 	    ((rb_const_entry_t*)v)->flag = flag;
2228 2232
 	}
2229 2233
 	else {
2230  
-	    if (i > 0)
2231  
-		rb_clear_cache_by_class(mod);
  2234
+	    if (i > 0) {
  2235
+		rb_clear_cache();
  2236
+	    }
2232 2237
 	    rb_name_error(id, "constant %"PRIsVALUE"::%"PRIsVALUE" not defined",
2233 2238
 			  rb_class_name(mod), QUOTE_ID(id));
2234 2239
 	}
2235 2240
     }
2236  
-    rb_clear_cache_by_class(mod);
  2241
+    rb_clear_cache();
2237 2242
 }
2238 2243
 
2239 2244
 /*
27  vm.c
@@ -71,6 +71,9 @@ static VALUE
71 71
 vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, VALUE self, VALUE defined_class,
72 72
 	       int argc, const VALUE *argv, const rb_block_t *blockptr);
73 73
 
  74
+static vm_state_version_t ruby_vm_global_state_version = 1;
  75
+static vm_state_version_t ruby_vm_sequence = 1;
  76
+
74 77
 #include "vm_insnhelper.h"
75 78
 #include "vm_insnhelper.c"
76 79
 #include "vm_exec.h"
@@ -84,6 +87,12 @@ vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, VALUE self, VALUE defined_class
84 87
 #define BUFSIZE 0x100
85 88
 #define PROCDEBUG 0
86 89
 
  90
+vm_state_version_t
  91
+rb_next_class_sequence()
  92
+{
  93
+    return NEXT_CLASS_SEQUENCE();
  94
+}
  95
+
87 96
 VALUE rb_cRubyVM;
88 97
 VALUE rb_cThread;
89 98
 VALUE rb_cEnv;
@@ -97,14 +106,6 @@ rb_event_flag_t ruby_vm_event_flags;
97 106
 
98 107
 static void thread_free(void *ptr);
99 108
 
100  
-void
101  
-rb_vm_change_state(void)
102  
-{
103  
-    INC_VM_STATE_VERSION();
104  
-}
105  
-
106  
-static void vm_clear_global_method_cache(void);
107  
-
108 109
 static void
109 110
 vm_clear_all_inline_method_cache(void)
110 111
 {
@@ -117,7 +118,6 @@ vm_clear_all_inline_method_cache(void)
117 118
 static void
118 119
 vm_clear_all_cache()
119 120
 {
120  
-    vm_clear_global_method_cache();
121 121
     vm_clear_all_inline_method_cache();
122 122
     ruby_vm_global_state_version = 1;
123 123
 }
@@ -2069,11 +2069,13 @@ vm_define_method(rb_thread_t *th, VALUE obj, ID id, VALUE iseqval,
2069 2069
     OBJ_WRITE(miseq->self, &miseq->klass, klass);
2070 2070
     miseq->defined_method_id = id;
2071 2071
     rb_add_method(klass, id, VM_METHOD_TYPE_ISEQ, miseq, noex);
  2072
+    rb_clear_cache_by_class(klass);
2072 2073
 
2073 2074
     if (!is_singleton && noex == NOEX_MODFUNC) {
2074  
-	rb_add_method(rb_singleton_class(klass), id, VM_METHOD_TYPE_ISEQ, miseq, NOEX_PUBLIC);
  2075
+	klass = rb_singleton_class(klass);
  2076
+	rb_add_method(klass, id, VM_METHOD_TYPE_ISEQ, miseq, NOEX_PUBLIC);
  2077
+	rb_clear_cache_by_class(klass);
2075 2078
     }
2076  
-    INC_VM_STATE_VERSION();
2077 2079
 }
2078 2080
 
2079 2081
 #define REWIND_CFP(expr) do { \
@@ -2122,7 +2124,8 @@ m_core_undef_method(VALUE self, VALUE cbase, VALUE sym)
2122 2124
 {
2123 2125
     REWIND_CFP({
2124 2126
 	rb_undef(cbase, SYM2ID(sym));
2125  
-	INC_VM_STATE_VERSION();
  2127
+	rb_clear_cache_by_class(cbase);
  2128
+	rb_clear_cache_by_class(self);
2126 2129
     });
2127 2130
     return Qnil;
2128 2131
 }
6  vm_core.h
@@ -127,7 +127,8 @@ typedef struct rb_compile_option_struct rb_compile_option_t;
127 127
 
128 128
 
129 129
 struct iseq_inline_cache_entry {
130  
-    VALUE ic_vmstat;
  130
+    vm_state_version_t ic_vmstat;
  131
+    vm_state_version_t ic_seq;
131 132
     VALUE ic_class;
132 133
     union {
133 134
 	size_t index;
@@ -157,7 +158,8 @@ typedef struct rb_call_info_struct {
157 158
     rb_iseq_t *blockiseq;
158 159
 
159 160
     /* inline cache: keys */
160  
-    VALUE vmstat;
  161
+    vm_state_version_t vmstat;
  162
+    vm_state_version_t seq;
161 163
     VALUE klass;
162 164
 
163 165
     /* inline cache: values */
6  vm_insnhelper.c
@@ -848,16 +848,18 @@ vm_search_method(rb_call_info_t *ci, VALUE recv)
848 848
     VALUE klass = CLASS_OF(recv);
849 849
 
850 850
 #if OPT_INLINE_METHOD_CACHE
851  
-    if (LIKELY(GET_VM_STATE_VERSION() == ci->vmstat && klass == ci->klass)) {
  851
+    if (LIKELY(GET_VM_STATE_VERSION() == ci->vmstat && RCLASS_EXT(klass)->seq == ci->seq && klass == ci->klass)) {
852 852
 	/* cache hit! */
853 853
 	return;
854 854
     }
855 855
 #endif
  856
+
856 857
     ci->me = rb_method_entry(klass, ci->mid, &ci->defined_class);
857  
-    ci->call = vm_call_general;
858 858
     ci->klass = klass;
  859
+    ci->call = vm_call_general;
859 860
 #if OPT_INLINE_METHOD_CACHE
860 861
     ci->vmstat = GET_VM_STATE_VERSION();
  862
+    ci->seq = RCLASS_EXT(klass)->seq;
861 863
 #endif
862 864
 }
863 865
 
3  vm_insnhelper.h
@@ -257,8 +257,7 @@ enum vm_regan_acttype {
257 257
     CALL_METHOD(ci); \
258 258
 } while (0)
259 259
 
260  
-static VALUE ruby_vm_global_state_version = 1;
261  
-
  260
+#define NEXT_CLASS_SEQUENCE() (++ruby_vm_sequence)
262 261
 #define GET_VM_STATE_VERSION() (ruby_vm_global_state_version)
263 262
 #define INC_VM_STATE_VERSION() do { \
264 263
     ruby_vm_global_state_version = (ruby_vm_global_state_version + 1); \
105  vm_method.c
@@ -2,9 +2,7 @@
2 2
  * This file is included by vm.c
3 3
  */
4 4
 
5  
-#define CACHE_SIZE 0x800
6  
-#define CACHE_MASK 0x7ff
7  
-#define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK)
  5
+#include "method.h"
8 6
 
9 7
 #define NOEX_NOREDEF 0
10 8
 #ifndef NOEX_NOREDEF
@@ -22,53 +20,32 @@ static void rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VAL
22 20
 #define singleton_undefined idSingleton_method_undefined
23 21
 #define attached            id__attached__
24 22
 
25  
-struct cache_entry {		/* method hash table. */
26  
-    VALUE filled_version;        /* filled state version */
27  
-    ID mid;			/* method's id */
28  
-    VALUE klass;		/* receiver's class */
29  
-    rb_method_entry_t *me;
30  
-    VALUE defined_class;
31  
-};
32  
-
33  
-static struct cache_entry cache[CACHE_SIZE];
34 23
 #define ruby_running (GET_VM()->running)
35 24
 /* int ruby_running = 0; */
36 25
 
37 26
 static void
38  
-vm_clear_global_method_cache(void)
  27
+rb_class_clear_method_cache(VALUE klass)
39 28
 {
40  
-    struct cache_entry *ent, *end;
41  
-
42  
-    ent = cache;
43  
-    end = ent + CACHE_SIZE;
44  
-    while (ent < end) {
45  
-	ent->filled_version = 0;
46  
-	ent++;
47  
-    }
  29
+    RCLASS_EXT(klass)->seq = rb_next_class_sequence();
  30
+    rb_class_foreach_subclass(klass, rb_class_clear_method_cache);
48 31
 }
49 32
 
50 33
 void
51 34
 rb_clear_cache(void)
52 35
 {
53  
-    rb_vm_change_state();
54  
-}
55  
-
56  
-static void
57  
-rb_clear_cache_for_undef(VALUE klass, ID id)
58  
-{
59  
-    rb_vm_change_state();
60  
-}
61  
-
62  
-static void
63  
-rb_clear_cache_by_id(ID id)
64  
-{
65  
-    rb_vm_change_state();
  36
+    INC_VM_STATE_VERSION();
66 37
 }
67 38
 
68 39
 void
69 40
 rb_clear_cache_by_class(VALUE klass)
70 41
 {
71  
-    rb_vm_change_state();
  42
+    if (klass && klass != Qundef) {
  43
+	if (klass == rb_cBasicObject || klass == rb_cObject || klass == rb_mKernel) {
  44
+	    INC_VM_STATE_VERSION();
  45
+	} else {
  46
+	    rb_class_clear_method_cache(klass);
  47
+	}
  48
+    }
72 49
 }
73 50
 
74 51
 VALUE
@@ -310,7 +287,7 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type,
310 287
 
311 288
     me = ALLOC(rb_method_entry_t);
312 289
 
313  
-    rb_clear_cache_by_id(mid);
  290
+    rb_clear_cache_by_class(klass);
314 291
 
315 292
     me->flag = NOEX_WITH_SAFE(noex);
316 293
     me->mark = 0;
@@ -472,6 +449,7 @@ rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *opts, rb_method_
472 449
     if (type != VM_METHOD_TYPE_UNDEF && type != VM_METHOD_TYPE_REFINED) {
473 450
 	method_added(klass, mid);
474 451
     }
  452
+    rb_clear_cache_by_class(klass);
475 453
     return me;
476 454
 }
477 455
 
@@ -540,26 +518,26 @@ rb_method_entry_at(VALUE klass, ID id)
540 518
  */
541 519
 rb_method_entry_t *
542 520
 rb_method_entry_get_without_cache(VALUE klass, ID id,
543  
-				  VALUE *defined_class_ptr)
  521
+				  VALUE *defined_class_ptr,
  522
+				  method_cache_entry_t *ent)
544 523
 {
545 524
     VALUE defined_class;
546 525
     rb_method_entry_t *me = search_method(klass, id, &defined_class);
547 526
 
548 527
     if (ruby_running) {
549  
-	struct cache_entry *ent;
550  
-	ent = cache + EXPR1(klass, id);
551  
-	ent->filled_version = GET_VM_STATE_VERSION();
552  
-	ent->klass = klass;
553  
-	ent->defined_class = defined_class;
  528
+	ent->seq = RCLASS_EXT(klass)->seq;
  529
+	ent->vm_state = GET_VM_STATE_VERSION();
554 530
 
555 531
 	if (UNDEFINED_METHOD_ENTRY_P(me)) {
556 532
 	    ent->mid = id;
557 533
 	    ent->me = 0;
  534
+	    ent->defined_class = defined_class;
558 535
 	    me = 0;
559 536
 	}
560 537
 	else {
561 538
 	    ent->mid = id;
562 539
 	    ent->me = me;
  540
+	    ent->defined_class = defined_class;
563 541
 	}
564 542
     }
565 543
 
@@ -568,22 +546,52 @@ rb_method_entry_get_without_cache(VALUE klass, ID id,
568 546
     return me;
569 547
 }
570 548
 
  549
+#if VM_DEBUG_VERIFY_METHOD_CACHE
  550
+static void
  551
+verify_method_cache(VALUE klass, ID id, VALUE defined_class, rb_method_entry_t *me)
  552
+{
  553
+    VALUE actual_defined_class;
  554
+    method_cache_entry_t ent;
  555
+    rb_method_entry_t *actual_me =
  556
+	rb_method_entry_get_without_cache(klass, id, &actual_defined_class, &ent);
  557
+
  558
+    if (me != actual_me || defined_class != actual_defined_class) {
  559
+	rb_bug("method cache verification failed");
  560
+    }
  561
+}
  562
+#endif
  563
+
571 564
 rb_method_entry_t *
572 565
 rb_method_entry(VALUE klass, ID id, VALUE *defined_class_ptr)
573 566
 {
574 567
 #if OPT_GLOBAL_METHOD_CACHE
575  
-    struct cache_entry *ent;
  568
+    method_cache_entry_t *ent;
  569
+
  570
+    if (RCLASS_EXT(klass)->mc_tbl == NULL) {
  571
+	RCLASS_EXT(klass)->mc_tbl = st_init_numtable();
  572
+    }
576 573
 
577  
-    ent = cache + EXPR1(klass, id);
578  
-    if (ent->filled_version == GET_VM_STATE_VERSION() &&
579  
-	ent->mid == id && ent->klass == klass) {
  574
+    if (!st_lookup(RCLASS_EXT(klass)->mc_tbl, (st_index_t)id, (st_data_t *)&ent)) {
  575
+	ent = calloc(1, sizeof(*ent));
  576
+	st_insert(RCLASS_EXT(klass)->mc_tbl, (st_index_t)id, (st_data_t)ent);
  577
+    }
  578
+
  579
+    if (ent->seq == RCLASS_EXT(klass)->seq &&
  580
+	ent->vm_state == GET_VM_STATE_VERSION() &&
  581
+	ent->mid == id) {
580 582
 	if (defined_class_ptr)
581 583
 	    *defined_class_ptr = ent->defined_class;
  584
+#if VM_DEBUG_VERIFY_METHOD_CACHE
  585
+	verify_method_cache(klass, id, ent->defined_class, ent->me);
  586
+#endif
582 587
 	return ent->me;
583 588
     }
  589
+#else
  590
+    method_cache_entry_t ent_;
  591
+    method_cache_entry_t* ent = &ent_;
584 592
 #endif
585 593
 
586  
-    return rb_method_entry_get_without_cache(klass, id, defined_class_ptr);
  594
+    return rb_method_entry_get_without_cache(klass, id, defined_class_ptr, ent);
587 595
 }
588 596
 
589 597
 static rb_method_entry_t *
@@ -687,7 +695,7 @@ remove_method(VALUE klass, ID mid)
687 695
     st_delete(RCLASS_M_TBL(klass), &key, &data);
688 696
 
689 697
     rb_vm_check_redefinition_opt_method(me, klass);
690  
-    rb_clear_cache_for_undef(klass, mid);
  698
+    rb_clear_cache_by_class(klass);
691 699
     rb_unlink_method_entry(me);
692 700
 
693 701
     CALL_METHOD_HOOK(self, removed, mid);
@@ -1220,6 +1228,7 @@ rb_alias(VALUE klass, ID name, ID def)
1220 1228
 
1221 1229
     if (flag == NOEX_UNDEF) flag = orig_me->flag;
1222 1230
     rb_method_entry_set(target_klass, name, orig_me, flag);
  1231
+    rb_clear_cache_by_class(target_klass);
1223 1232
 }
1224 1233
 
1225 1234
 /*
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.