0003-Index-LOADED_FEATURES-so-require-isn-t-so-slow.patch

Patch 3 - index for $LOADED_FEATURES - Greg Price, 10/14/2012 01:56 PM

Download (11.2 KB)

View differences:

ChangeLog
1
Tue Sep  4 14:15:18 2012  Greg Price  <price@mit.edu>
2

  
3
	* load.c (rb_feature_p, rb_provide_feature, et al.):
4
	  index $LOADED_FEATURES so that require isn't so slow.
5

  
6
	* load.c (rb_provide_feature, get_loaded_features_index): ensure
7
	  that $LOADED_FEATURES entries are frozen strings.  The user
8
	  must mutate $LOADED_FEATURES itself rather than its individual
9
	  entries.
10

  
11
	* vm_core.h (rb_vm_struct): add fields.
12

  
13
	* vm.c (rb_vm_mark): mark new fields.
14

  
15
	* include/ruby/intern.h (rb_hash_clear): declare function.
16

  
17
	* hash.c (rb_hash_clear): make function non-static.
18

  
1 19
Tue Sep  4 14:16:52 2012  Greg Price  <price@mit.edu>
2 20

  
3 21
	* array.c (rb_ary_shared_with_p): new function.
hash.c
1103 1103
 *
1104 1104
 */
1105 1105

  
1106
static VALUE
1106
VALUE
1107 1107
rb_hash_clear(VALUE hash)
1108 1108
{
1109 1109
    rb_hash_modify_check(hash);
include/ruby/intern.h
460 460
VALUE rb_hash_lookup2(VALUE, VALUE, VALUE);
461 461
VALUE rb_hash_fetch(VALUE, VALUE);
462 462
VALUE rb_hash_aset(VALUE, VALUE, VALUE);
463
VALUE rb_hash_clear(VALUE);
463 464
VALUE rb_hash_delete_if(VALUE);
464 465
VALUE rb_hash_delete(VALUE,VALUE);
465 466
typedef VALUE rb_hash_update_func(VALUE newkey, VALUE oldkey, VALUE value);
load.c
18 18
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)
19 19
#endif
20 20

  
21

  
22 21
static const char *const loadable_ext[] = {
23 22
    ".rb", DLEXT,
24 23
#ifdef DLEXT2
......
63 62
    return GET_VM()->loaded_features;
64 63
}
65 64

  
65
static void
66
reset_loaded_features_snapshot(void)
67
{
68
    rb_vm_t *vm = GET_VM();
69
    rb_ary_replace(vm->loaded_features_snapshot, vm->loaded_features);
70
}
71

  
72
static VALUE
73
get_loaded_features_index_raw(void)
74
{
75
    return GET_VM()->loaded_features_index;
76
}
77

  
66 78
static st_table *
67 79
get_loading_table(void)
68 80
{
69 81
    return GET_VM()->loading_table;
70 82
}
71 83

  
84
static void
85
features_index_add_single(VALUE short_feature, VALUE offset)
86
{
87
    VALUE features_index, this_feature_index;
88
    features_index = get_loaded_features_index_raw();
89
    if ((this_feature_index = rb_hash_lookup(features_index, short_feature)) == Qnil) {
90
	this_feature_index = rb_ary_new();
91
	rb_hash_aset(features_index, short_feature, this_feature_index);
92
    }
93
    rb_ary_push(this_feature_index, offset);
94
}
95

  
96
/* Add to the loaded-features index all the required entries for
97
   `feature`, located at `offset` in $LOADED_FEATURES.  We add an
98
   index entry at each string `short_feature` for which
99
     feature == "#{prefix}#{short_feature}#{e}"
100
   where `e` is empty or matches %r{^\.[^./]*$}, and `prefix` is empty
101
   or ends in '/'.  This maintains the invariant that `rb_feature_p()`
102
   relies on for its fast lookup.
103
*/
104
static void
105
features_index_add(VALUE feature, VALUE offset)
106
{
107
    VALUE short_feature;
108
    const char *feature_str, *feature_end, *ext, *p;
109

  
110
    feature_str = StringValuePtr(feature);
111
    feature_end = feature_str + RSTRING_LEN(feature);
112

  
113
    for (ext = feature_end; ext > feature_str; ext--)
114
      if (*ext == '.' || *ext == '/')
115
	break;
116
    if (*ext != '.')
117
      ext = NULL;
118
    /* Now `ext` points to the only string matching %r{^\.[^./]*$} that is
119
       at the end of `feature`, or is NULL if there is no such string. */
120

  
121
    p = ext ? ext : feature_end;
122
    while (1) {
123
	p--;
124
	while (p >= feature_str && *p != '/')
125
	    p--;
126
	if (p < feature_str)
127
	    break;
128
	/* Now *p == '/'.  We reach this point for every '/' in `feature`. */
129
	short_feature = rb_str_substr(feature, p + 1 - feature_str, feature_end - p - 1);
130
	features_index_add_single(short_feature, offset);
131
	if (ext) {
132
	    short_feature = rb_str_substr(feature, p + 1 - feature_str, ext - p - 1);
133
	    features_index_add_single(short_feature, offset);
134
	}
135
    }
136
    features_index_add_single(feature, offset);
137
    if (ext) {
138
	short_feature = rb_str_substr(feature, 0, ext - feature_str);
139
	features_index_add_single(short_feature, offset);
140
    }
141
}
142

  
143
static VALUE
144
get_loaded_features_index(void)
145
{
146
    VALUE features;
147
    int i;
148
    rb_vm_t *vm = GET_VM();
149

  
150
    if (!rb_ary_shared_with_p(vm->loaded_features_snapshot, vm->loaded_features)) {
151
	/* The sharing was broken; something (other than us in rb_provide_feature())
152
	   modified loaded_features.  Rebuild the index. */
153
	rb_hash_clear(vm->loaded_features_index);
154
	features = vm->loaded_features;
155
	for (i = 0; i < RARRAY_LEN(features); i++) {
156
	    VALUE entry, as_str;
157
	    as_str = entry = rb_ary_entry(features, i);
158
	    StringValue(as_str);
159
	    if (as_str != entry)
160
		rb_ary_store(features, i, as_str);
161
	    rb_str_freeze(as_str);
162
	    features_index_add(as_str, INT2FIX(i));
163
	}
164
	reset_loaded_features_snapshot();
165
    }
166
    return vm->loaded_features_index;
167
}
168

  
72 169
/* This searches `load_path` for a value such that
73 170
     name == "#{load_path[i]}/#{feature}"
74 171
   if `feature` is a suffix of `name`, or otherwise
......
142 239
static int
143 240
rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const char **fn)
144 241
{
145
    VALUE v, features, p, load_path = 0;
242
    VALUE features, features_index, feature_val, this_feature_index, v, p, load_path = 0;
146 243
    const char *f, *e;
147 244
    long i, len, elen, n;
148 245
    st_table *loading_tbl;
......
161 258
	type = 0;
162 259
    }
163 260
    features = get_loaded_features();
164
    for (i = 0; i < RARRAY_LEN(features); ++i) {
165
	/* This loop searches `features` for an entry such that either
166
	     "#{features[i]}" == "#{load_path[j]}/#{feature}#{e}"
167
	   for some j, or
168
	     "#{features[i]}" == "#{feature}#{e}"
169
	   Here `e` is an "allowed" extension -- either empty or one
170
	   of the extensions accepted by IS_RBEXT, IS_SOEXT, or
171
	   IS_DLEXT.  Further, if `ext && rb` then `IS_RBEXT(e)`,
172
	   and if `ext && !rb` then `IS_SOEXT(e) || IS_DLEXT(e)`.
173

  
174
	   If `expanded`, then only the latter form (without
175
	   load_path[j]) is accepted.  Otherwise either form is
176
	   accepted, *unless* `ext` is false and an otherwise-matching
177
	   entry of the first form is preceded by an entry of the form
178
	     "#{features[i2]}" == "#{load_path[j2]}/#{feature}#{e2}"
179
	   where `e2` matches /^\.[^./]*$/ but is not an allowed extension.
180
	   After a "distractor" entry of this form, only entries of the
181
	   form "#{feature}#{e}" are accepted.
182
	*/
183

  
184
	v = RARRAY_PTR(features)[i];
261
    features_index = get_loaded_features_index();
262

  
263
    feature_val = rb_str_new(feature, len);
264
    this_feature_index = rb_hash_lookup(features_index, feature_val);
265
    /* We search `features` for an entry such that either
266
         "#{features[i]}" == "#{load_path[j]}/#{feature}#{e}"
267
       for some j, or
268
         "#{features[i]}" == "#{feature}#{e}"
269
       Here `e` is an "allowed" extension -- either empty or one
270
       of the extensions accepted by IS_RBEXT, IS_SOEXT, or
271
       IS_DLEXT.  Further, if `ext && rb` then `IS_RBEXT(e)`,
272
       and if `ext && !rb` then `IS_SOEXT(e) || IS_DLEXT(e)`.
273

  
274
       If `expanded`, then only the latter form (without load_path[j])
275
       is accepted.  Otherwise either form is accepted, *unless* `ext`
276
       is false and an otherwise-matching entry of the first form is
277
       preceded by an entry of the form
278
         "#{features[i2]}" == "#{load_path[j2]}/#{feature}#{e2}"
279
       where `e2` matches %r{^\.[^./]*$} but is not an allowed extension.
280
       After a "distractor" entry of this form, only entries of the
281
       form "#{feature}#{e}" are accepted.
282

  
283
       In `rb_provide_feature()` and `get_loaded_features_index()` we
284
       maintain an invariant that the array `this_feature_index` will
285
       point to every entry in `features` which has the form
286
         "#{prefix}#{feature}#{e}"
287
       where `e` is empty or matches %r{^\.[^./]*$}, and `prefix` is empty
288
       or ends in '/'.  This includes both match forms above, as well
289
       as any distractors, so we may ignore all other entries in `features`.
290
     */
291
    for (i = 0; this_feature_index != Qnil && i < RARRAY_LEN(this_feature_index); i++) {
292
	long index = FIX2LONG(rb_ary_entry(this_feature_index, i));
293
	v = RARRAY_PTR(features)[index];
185 294
	f = StringValuePtr(v);
186 295
	if ((n = RSTRING_LEN(v)) < len) continue;
187 296
	if (strncmp(f, feature, len) != 0) {
......
204 313
	    return 'r';
205 314
	}
206 315
    }
316

  
207 317
    loading_tbl = get_loading_table();
208 318
    if (loading_tbl) {
209 319
	f = 0;
......
283 393
static void
284 394
rb_provide_feature(VALUE feature)
285 395
{
286
    if (OBJ_FROZEN(get_loaded_features())) {
396
    VALUE features;
397

  
398
    features = get_loaded_features();
399
    if (OBJ_FROZEN(features)) {
287 400
	rb_raise(rb_eRuntimeError,
288 401
		 "$LOADED_FEATURES is frozen; cannot append feature");
289 402
    }
290
    rb_ary_push(get_loaded_features(), feature);
403
    rb_str_freeze(feature);
404

  
405
    rb_ary_push(features, feature);
406
    features_index_add(feature, INT2FIX(RARRAY_LEN(features)-1));
407
    reset_loaded_features_snapshot();
291 408
}
292 409

  
293 410
void
......
828 945
    rb_define_virtual_variable("$\"", get_loaded_features, 0);
829 946
    rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0);
830 947
    vm->loaded_features = rb_ary_new();
948
    vm->loaded_features_snapshot = rb_ary_new();
949
    vm->loaded_features_index = rb_hash_new();
831 950

  
832 951
    rb_define_global_function("load", rb_f_load, -1);
833 952
    rb_define_global_function("require", rb_f_require, 1);
vm.c
1501 1501
	RUBY_MARK_UNLESS_NULL(vm->mark_object_ary);
1502 1502
	RUBY_MARK_UNLESS_NULL(vm->load_path);
1503 1503
	RUBY_MARK_UNLESS_NULL(vm->loaded_features);
1504
	RUBY_MARK_UNLESS_NULL(vm->loaded_features_snapshot);
1505
	RUBY_MARK_UNLESS_NULL(vm->loaded_features_index);
1504 1506
	RUBY_MARK_UNLESS_NULL(vm->top_self);
1505 1507
	RUBY_MARK_UNLESS_NULL(vm->coverages);
1506 1508
	rb_gc_mark_locations(vm->special_exceptions, vm->special_exceptions + ruby_special_error_count);
vm_core.h
322 322
    VALUE top_self;
323 323
    VALUE load_path;
324 324
    VALUE loaded_features;
325
    VALUE loaded_features_snapshot;
326
    VALUE loaded_features_index;
325 327
    struct st_table *loading_table;
326 328

  
327 329
    /* signal */
328
-