aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornormal <normal@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2014-12-09 15:43:49 +0000
committernormal <normal@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2014-12-09 15:43:49 +0000
commit65651b34b1b5c2ef556ed44d5ebbd98838cfe8e6 (patch)
treeed0f774e4b43674ffc54dad5d0a7392d7904a7ab
parent93db27d51fcdc22a0ff6a1d3528b7017e9a92b55 (diff)
downloadruby-65651b34b1b5c2ef556ed44d5ebbd98838cfe8e6.tar.gz
struct: avoid all O(n) behavior on access
This avoids O(n) on lookups with structs over 10 members. This also avoids O(n) behavior on all assignments on Struct members. Members 0..9 still use existing C methods to read in O(1) time Benchmark results: vm2_struct_big_aref_hi* 1.305 vm2_struct_big_aref_lo* 1.157 vm2_struct_big_aset* 3.306 vm2_struct_small_aref* 1.015 vm2_struct_small_aset* 3.273 Note: I chose use loading instructions from an array instead of writing directly to linked-lists in compile.c for ease-of-maintainability. We may move the method definitions to prelude.rb-like files in the future. I have also tested this patch with the following patch to disable the C ref_func methods and ensured the test suite and rubyspec works --- a/struct.c +++ b/struct.c @@ -209,7 +209,7 @@ setup_struct(VALUE nstr, VALUE members) ID id = SYM2ID(ptr_members[i]); VALUE off = LONG2NUM(i); - if (i < N_REF_FUNC) { + if (0 && i < N_REF_FUNC) { rb_define_method_id(nstr, id, ref_func[i], 0); } else { * iseq.c (rb_method_for_self_aref, rb_method_for_self_aset): new methods to generate bytecode for struct.c [Feature #10575] * struct.c (rb_struct_ref, rb_struct_set): remove (define_aref_method, define_aset_method): new functions (setup_struct): use new functions * test/ruby/test_struct.rb: add test for struct >10 members * benchmark/bm_vm2_struct_big_aref_hi.rb: new benchmark * benchmark/bm_vm2_struct_big_aref_lo.rb: ditto * benchmark/bm_vm2_struct_big_aset.rb: ditto * benchmark/bm_vm2_struct_small_aref.rb: ditto * benchmark/bm_vm2_struct_small_aset.rb: ditto git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@48748 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog15
-rw-r--r--benchmark/bm_vm2_struct_big_aref_hi.rb7
-rw-r--r--benchmark/bm_vm2_struct_big_aref_lo.rb7
-rw-r--r--benchmark/bm_vm2_struct_big_aset.rb7
-rw-r--r--benchmark/bm_vm2_struct_small_aref.rb7
-rw-r--r--benchmark/bm_vm2_struct_small_aset.rb7
-rw-r--r--iseq.c101
-rw-r--r--struct.c61
-rw-r--r--test/ruby/test_struct.rb9
9 files changed, 187 insertions, 34 deletions
diff --git a/ChangeLog b/ChangeLog
index bb994480e9..5fd9cb8755 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+Wed Dec 10 00:42:13 2014 Eric Wong <e@80x24.org>
+
+ * iseq.c (rb_method_for_self_aref, rb_method_for_self_aset):
+ new methods to generate bytecode for struct.c
+ [Feature #10575]
+ * struct.c (rb_struct_ref, rb_struct_set): remove
+ (define_aref_method, define_aset_method): new functions
+ (setup_struct): use new functions
+ * test/ruby/test_struct.rb: add test for struct >10 members
+ * benchmark/bm_vm2_struct_big_aref_hi.rb: new benchmark
+ * benchmark/bm_vm2_struct_big_aref_lo.rb: ditto
+ * benchmark/bm_vm2_struct_big_aset.rb: ditto
+ * benchmark/bm_vm2_struct_small_aref.rb: ditto
+ * benchmark/bm_vm2_struct_small_aset.rb: ditto
+
Tue Dec 9 20:24:41 2014 SHIBATA Hiroshi <shibata.hiroshi@gmail.com>
* string.c: [DOC] Add missing documentation around String#chomp.
diff --git a/benchmark/bm_vm2_struct_big_aref_hi.rb b/benchmark/bm_vm2_struct_big_aref_hi.rb
new file mode 100644
index 0000000000..22cb26b0a5
--- /dev/null
+++ b/benchmark/bm_vm2_struct_big_aref_hi.rb
@@ -0,0 +1,7 @@
+s = Struct.new(*('a'..'z').map { |x| x.to_sym })
+x = s.new
+i = 0
+while i<6_000_000 # benchmark loop 2
+ i += 1
+ x.z # x[25]
+end
diff --git a/benchmark/bm_vm2_struct_big_aref_lo.rb b/benchmark/bm_vm2_struct_big_aref_lo.rb
new file mode 100644
index 0000000000..5e61a7087e
--- /dev/null
+++ b/benchmark/bm_vm2_struct_big_aref_lo.rb
@@ -0,0 +1,7 @@
+s = Struct.new(*('a'..'z').map { |x| x.to_sym })
+x = s.new
+i = 0
+while i<6_000_000 # benchmark loop 2
+ i += 1
+ x.k # x[10]
+end
diff --git a/benchmark/bm_vm2_struct_big_aset.rb b/benchmark/bm_vm2_struct_big_aset.rb
new file mode 100644
index 0000000000..5a1c3d16f3
--- /dev/null
+++ b/benchmark/bm_vm2_struct_big_aset.rb
@@ -0,0 +1,7 @@
+s = Struct.new(*('a'..'z').map { |x| x.to_sym })
+x = s.new
+i = 0
+while i<6_000_000 # benchmark loop 2
+ i += 1
+ x.k = i # x[10] = i
+end
diff --git a/benchmark/bm_vm2_struct_small_aref.rb b/benchmark/bm_vm2_struct_small_aref.rb
new file mode 100644
index 0000000000..8eaa555b41
--- /dev/null
+++ b/benchmark/bm_vm2_struct_small_aref.rb
@@ -0,0 +1,7 @@
+s = Struct.new(:a, :b, :c)
+x = s.new
+i = 0
+while i<6_000_000 # benchmark loop 2
+ i += 1
+ x.a
+end
diff --git a/benchmark/bm_vm2_struct_small_aset.rb b/benchmark/bm_vm2_struct_small_aset.rb
new file mode 100644
index 0000000000..ecd0f95669
--- /dev/null
+++ b/benchmark/bm_vm2_struct_small_aset.rb
@@ -0,0 +1,7 @@
+s = Struct.new(:a, :b, :c)
+x = s.new
+i = 0
+while i<6_000_000 # benchmark loop 2
+ i += 1
+ x.a = i
+end
diff --git a/iseq.c b/iseq.c
index 8f84f88c14..15d1de698c 100644
--- a/iseq.c
+++ b/iseq.c
@@ -548,6 +548,107 @@ iseq_load(VALUE self, VALUE data, VALUE parent, VALUE opt)
return iseqval;
}
+rb_iseq_t *
+rb_method_for_self_aref(VALUE name, VALUE arg)
+{
+ VALUE iseqval = iseq_alloc(rb_cISeq);
+ rb_iseq_t *iseq;
+ VALUE path = rb_str_new2("<compiled>");
+ VALUE lineno = INT2FIX(1);
+ VALUE parent = 0;
+ VALUE misc, locals, params, exception, body, send_arg;
+ int flag = VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE;
+
+ GetISeqPtr(iseqval, iseq);
+ iseq->self = iseqval;
+ iseq->local_iseq = iseq;
+
+ prepare_iseq_build(iseq, rb_sym2str(name), path, path, lineno, parent,
+ ISEQ_TYPE_METHOD, &COMPILE_OPTION_DEFAULT);
+
+ misc = params = rb_hash_new(); /* empty */
+ locals = exception = rb_ary_new(); /* empty */
+ body = rb_ary_new();
+
+#define S(s) ID2SYM(rb_intern(#s))
+ /* def name; self[arg]; end */
+ rb_ary_push(body, lineno);
+ rb_ary_push(body, rb_ary_new3(1, S(putself)));
+ rb_ary_push(body, rb_ary_new3(2, S(putobject), arg));
+
+ /* {:mid=>:[], :flag=>264, :blockptr=>nil, :orig_argc=>1} */
+ send_arg = rb_hash_new();
+ rb_hash_aset(send_arg, S(mid), ID2SYM(idAREF));
+ rb_hash_aset(send_arg, S(flag), INT2FIX(flag));
+ rb_hash_aset(send_arg, S(blockptr), Qnil);
+ rb_hash_aset(send_arg, S(orig_argc), INT2FIX(1));
+
+ /* we do not want opt_aref for struct */
+ rb_ary_push(body, rb_ary_new3(2, S(opt_send_without_block), send_arg));
+ rb_ary_push(body, rb_ary_new3(1, S(leave)));
+#undef S
+
+ rb_iseq_build_from_ary(iseq, misc, locals, params, exception, body);
+ cleanup_iseq_build(iseq);
+
+ return iseq;
+}
+
+rb_iseq_t *
+rb_method_for_self_aset(VALUE name, VALUE arg)
+{
+ VALUE iseqval = iseq_alloc(rb_cISeq);
+ rb_iseq_t *iseq;
+ VALUE path = rb_str_new2("<compiled>");
+ VALUE lineno = INT2FIX(1);
+ VALUE parent = 0;
+ VALUE misc, locals, params, exception, body, send_arg;
+ int flag = VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE;
+
+ GetISeqPtr(iseqval, iseq);
+ iseq->self = iseqval;
+ iseq->local_iseq = iseq;
+
+ prepare_iseq_build(iseq, rb_sym2str(name), path, path, lineno, parent,
+ ISEQ_TYPE_METHOD, &COMPILE_OPTION_DEFAULT);
+
+ /* def name=(val); self[arg] = val; end */
+#define S(s) ID2SYM(rb_intern(#s))
+ misc = rb_hash_new(); /* empty */
+ locals = rb_ary_new3(1, S(val));
+ params = rb_hash_new();
+ exception = rb_ary_new(); /* empty */
+ body = rb_ary_new();
+
+ rb_hash_aset(params, S(lead_num), INT2FIX(1));
+
+ rb_ary_push(body, lineno);
+ rb_ary_push(body, rb_ary_new3(1, S(putnil)));
+ rb_ary_push(body, rb_ary_new3(1, S(putself)));
+ rb_ary_push(body, rb_ary_new3(2, S(putobject), arg));
+ rb_ary_push(body, rb_ary_new3(3, S(getlocal), INT2FIX(2), INT2FIX(0)));
+ rb_ary_push(body, rb_ary_new3(2, S(setn), INT2FIX(3)));
+
+ /* {:mid=>:[]=, :flag=>264, :blockptr=>nil, :orig_argc=>2} */
+ send_arg = rb_hash_new();
+ rb_hash_aset(send_arg, S(mid), ID2SYM(idASET));
+ rb_hash_aset(send_arg, S(flag), INT2FIX(flag));
+ rb_hash_aset(send_arg, S(blockptr), Qnil);
+ rb_hash_aset(send_arg, S(orig_argc), INT2FIX(2));
+
+ /* we do not want opt_aset for struct */
+ rb_ary_push(body, rb_ary_new3(2, S(opt_send_without_block), send_arg));
+
+ rb_ary_push(body, rb_ary_new3(1, S(pop)));
+ rb_ary_push(body, rb_ary_new3(1, S(leave)));
+#undef S
+
+ rb_iseq_build_from_ary(iseq, misc, locals, params, exception, body);
+ cleanup_iseq_build(iseq);
+
+ return iseq;
+}
+
/*
* :nodoc:
*/
diff --git a/struct.c b/struct.c
index 93c55999c0..3a2ea47434 100644
--- a/struct.c
+++ b/struct.c
@@ -10,6 +10,11 @@
**********************************************************************/
#include "internal.h"
+#include "vm_core.h"
+#include "method.h"
+
+rb_iseq_t *rb_method_for_self_aref(VALUE name, VALUE arg);
+rb_iseq_t *rb_method_for_self_aset(VALUE name, VALUE arg);
VALUE rb_cStruct;
static ID id_members;
@@ -105,12 +110,6 @@ rb_struct_getmember(VALUE obj, ID id)
UNREACHABLE;
}
-static VALUE
-rb_struct_ref(VALUE obj)
-{
- return rb_struct_getmember(obj, rb_frame_this_func());
-}
-
static VALUE rb_struct_ref0(VALUE obj) {return RSTRUCT_GET(obj, 0);}
static VALUE rb_struct_ref1(VALUE obj) {return RSTRUCT_GET(obj, 1);}
static VALUE rb_struct_ref2(VALUE obj) {return RSTRUCT_GET(obj, 2);}
@@ -145,32 +144,6 @@ rb_struct_modify(VALUE s)
}
static VALUE
-rb_struct_set(VALUE obj, VALUE val)
-{
- VALUE members, slot, fsym;
- long i, len;
- ID fid = rb_frame_this_func();
- ID this_func = fid;
-
- members = rb_struct_members(obj);
- len = RARRAY_LEN(members);
- rb_struct_modify(obj);
- fid = rb_id_attrget(fid);
- if (!fid) not_a_member(this_func);
- fsym = ID2SYM(fid);
- for (i=0; i<len; i++) {
- slot = RARRAY_AREF(members, i);
- if (slot == fsym) {
- RSTRUCT_SET(obj, i, val);
- return val;
- }
- }
- not_a_member(fid);
-
- UNREACHABLE;
-}
-
-static VALUE
anonymous_struct(VALUE klass)
{
VALUE nstr;
@@ -199,6 +172,24 @@ new_struct(VALUE name, VALUE super)
return rb_define_class_id_under(super, id, super);
}
+static void
+define_aref_method(VALUE nstr, VALUE name, VALUE off)
+{
+ rb_iseq_t *iseq = rb_method_for_self_aref(name, off);
+
+ rb_add_method(nstr, SYM2ID(name), VM_METHOD_TYPE_ISEQ, iseq, NOEX_PUBLIC);
+ RB_GC_GUARD(iseq->self);
+}
+
+static void
+define_aset_method(VALUE nstr, VALUE name, VALUE off)
+{
+ rb_iseq_t *iseq = rb_method_for_self_aset(name, off);
+
+ rb_add_method(nstr, SYM2ID(name), VM_METHOD_TYPE_ISEQ, iseq, NOEX_PUBLIC);
+ RB_GC_GUARD(iseq->self);
+}
+
static VALUE
setup_struct(VALUE nstr, VALUE members)
{
@@ -216,13 +207,15 @@ setup_struct(VALUE nstr, VALUE members)
len = RARRAY_LEN(members);
for (i=0; i< len; i++) {
ID id = SYM2ID(ptr_members[i]);
+ VALUE off = LONG2NUM(i);
+
if (i < N_REF_FUNC) {
rb_define_method_id(nstr, id, ref_func[i], 0);
}
else {
- rb_define_method_id(nstr, id, rb_struct_ref, 0);
+ define_aref_method(nstr, ptr_members[i], off);
}
- rb_define_method_id(nstr, rb_id_attrset(id), rb_struct_set, 1);
+ define_aset_method(nstr, ID2SYM(rb_id_attrset(id)), off);
}
return nstr;
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index 71d93fb09d..8307d7932e 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -186,6 +186,15 @@ module TestStruct
assert_raise(ArgumentError) { o.select(1) }
end
+ def test_big_struct
+ klass1 = @Struct.new(*('a'..'z').map(&:to_sym))
+ o = klass1.new
+ assert_nil o.z
+ assert_equal(:foo, o.z = :foo)
+ assert_equal(:foo, o.z)
+ assert_equal(:foo, o[25])
+ end
+
def test_equal
klass1 = @Struct.new(:a)
klass2 = @Struct.new(:a, :b)