1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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
This commit is contained in:
normal 2014-12-09 15:43:49 +00:00
parent 93db27d51f
commit 65651b34b1
9 changed files with 187 additions and 34 deletions

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

101
iseq.c
View file

@ -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:
*/

View file

@ -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);}
@ -144,32 +143,6 @@ rb_struct_modify(VALUE s)
rb_check_trusted(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)
{
@ -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;

View file

@ -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)