Rubinius Eurucamp 2012 Workshop

Rubinius Eurucamp 2012 Workshop

Slides for the Rubinius workshop given at Eurucamp 2012

B012094b37ab6946c44eaa41d7828478?s=128

Dirkjan Bussink

August 17, 2012
Tweet

Transcript

  1. Rubinius Use Ruby

  2. Dirkjan Bussink d.bussink@gmail.com

  3. None
  4. Workshop Hacking on Rubinius

  5. Performance Fix a GC crash

  6. None
  7. Benchmarking

  8. ./bin/benchmark benchmark/core/string/bench_case.rb

  9. What is off?

  10. string upcase from uppercase 366824.2 (±4.1%) i/s string downcase from

    lowercase 2255986.4 (±1.9%) i/s
  11. Profiling

  12. ./bin/benchmark --profile \ benchmark/core/string/bench_upcase.rb ./bin/benchmark --profile \ benchmark/core/string/bench_downcase.rb

  13. % cumulative self self total time seconds seconds calls ms/call

    ms/call name ------------------------------------------------------------ 30.12 4.06 2.14 4093941 0.00 0.00 String#downcase 25.72 1.92 1.82 4093941 0.00 0.00 String#transform 21.34 5.50 1.51 298010 0.01 0.02 Object::__script__<14> {} 6.25 0.64 0.44 298020 0.00 0.00 Time#<=> 4.15 7.04 0.29 1 294.15 7036.42 Benchmark::ips<153> {}
  14. % cumulative self self total time seconds seconds calls ms/call

    ms/call name ------------------------------------------------------------ 42.18 5.89 3.02 207592 0.01 0.03 String#upcase! 21.60 1.88 1.55 4982208 0.00 0.00 Rubinius::CType.islower 5.38 0.49 0.39 207678 0.00 0.00 String#initialize_copy 4.89 0.73 0.35 207643 0.00 0.00 Rubinius::ByteArray#dup 3.16 0.34 0.23 4982208 0.00 0.00 Rubinius::ByteArray#[] 3.06 0.34 0.22 4982240 0.00 0.00 Fixnum#>= 2.24 6.69 0.16 207592 0.00 0.03 String#upcase 2.09 0.88 0.15 207730 0.00 0.00 String#modify! 2.08 0.20 0.15 207674 0.00 0.00 Rubinius::ByteArray.new 2.02 0.64 0.14 207645 0.00 0.00 String#dup 1.90 0.16 0.14 207643 0.00 0.00 Kernel#initialize_copy 1.40 6.79 0.10 50092 0.00 0.14 Object::__script__<13> {} 1.24 0.13 0.09 50102 0.00 0.00 Time#<=> 0.82 7.11 0.06 1 58.61 7108.64 Benchmark::ips<153> {}
  15. Structure

  16. kernel/alpha.rb kernel/bootstrap/*.rb kernel/platform/*.rb kernel/common/*.rb kernel/delta/*.rb

  17. vm/builtin/*.(h|c)pp vm/gc/*.(h|c)pp vm/llvm/*.(h|c)pp vm/capi/*.(h|c)pp vm/*.(h|c)pp

  18. Why is it slower?

  19. Implement a fix

  20. None
  21. None
  22. Reproduction

  23. None
  24. veritas $ until [ $? != 0 ]; do ../rubinius/bin/rbx

    -Xint -S rake spec:unit; done
  25. [BUG: ObjectHeader claimed to be empty or already containing object

    id] 2 rbx 0x0000000101ec9f73 rubinius::bug(char const*) + 67 3 rbx 0x0000000101f8ec43 rubinius::ObjectHeader::set_object_id(rubinius::State*, rubinius::ObjectMemory*, unsigned int) + 121 4 rbx 0x0000000101f8832d rubinius::ObjectMemory::assign_object_id(rubinius::State*, rubinius::Object*) + 127 5 rbx 0x0000000101ff0a26 rubinius::Object::id(rubinius::State*) + 84 6 rbx 0x0000000101f61500 rubinius::Primitives::object_id(rubinius::State*, rubinius::CallFrame*, rubinius::Executable*, rubinius::Module*, rubinius::Arguments&) + 212 7 ??? 0x000000010485a680 0x0 + 4370835072 8 rbx 0x0000000101ed26ec rubinius::VMMethod::interpreter(rubinius::State*, rubinius::VMMethod*, rubinius::InterpreterCallFrame*) + 9708 9 rbx 0x0000000101fbeb66 rubinius::Object* rubinius::VMMethod::execute_specialized<rubinius::OneArgument>(rubinius::State*, rubinius::CallFrame*, rubinius::Executable*, rubinius::Module*, rubinius::Arguments&) + 838 10 rbx 0x0000000101ff07b4 rubinius::Object::send_prim(rubinius::State*, rubinius::CallFrame*, rubinius::Executable*, rubinius::Module*, rubinius::Arguments&, rubinius::Symbol*) + 282 11 rbx 0x0000000101ff07f8 rubinius::Object::private_send_prim(rubinius::State*, rubinius::CallFrame*, rubinius::Executable*, rubinius::Module*, rubinius::Arguments&) + 28 12 rbx 0x0000000101f628b0 rubinius::Primitives::object_send(rubinius::State*, rubinius::CallFrame*, rubinius::Executable*, rubinius::Module*, rubinius::Arguments&) + 130 13 rbx 0x0000000101ed25a6 rubinius::VMMethod::interpreter(rubinius::State*, rubinius::VMMethod*, rubinius::InterpreterCallFrame*) + 9382 14 rbx 0x0000000101fc6241 rubinius::BlockEnvironment::execute_interpreter(rubinius::State*, rubinius::CallFrame*, rubinius::BlockEnvironment*, rubinius::Arguments&, rubinius::BlockInvocation&) + 1013 15 rbx 0x0000000101fc6378 rubinius::BlockEnvironment::invoke(rubinius::State*, rubinius::CallFrame*, rubinius::BlockEnvironment*, rubinius::Arguments&, rubinius::BlockInvocation&) + 260 16 rbx 0x0000000101fc6682 rubinius::BlockEnvironment::call(rubinius::State*, rubinius::CallFrame*, rubinius::Arguments&, int) + 68 17 rbx 0x0000000101ed38be rubinius::VMMethod::interpreter(rubinius::State*, rubinius::VMMethod*, rubinius::InterpreterCallFrame*) + 14270
  26. // Run only while om's lock is held. void ObjectHeader::set_object_id(STATE,

    ObjectMemory* om, uint32_t id) { // Just ignore trying to reset it to 0 for now. if(id == 0) return; // Construct 2 new headers: one is the version we hope that // is in use and the other is what we want it to be. The CAS // the new one into place. HeaderWord orig = header; orig.f.inflated = 0; orig.f.meaning = eAuxWordEmpty; orig.f.aux_word = 0; HeaderWord new_val = orig; new_val.f.meaning = eAuxWordObjID; new_val.f.aux_word = id; if(header.atomic_set(orig, new_val)) return; orig = header; if(orig.f.inflated) { ObjectHeader::header_to_inflated_header(orig)->set_object_id(id); return; } switch(orig.f.meaning) { case eAuxWordEmpty: case eAuxWordObjID: rubinius::bug("ObjectHeader claimed to be empty or already containing object id"); case eAuxWordLock: case eAuxWordHandle: // not inflated, and the aux_word is being used for locking // or a handle. // Inflate! om->inflate_for_id(state, this, id); } }
  27. rubinius $ rake clean rubinius $ DEV=1 rake build

  28. [BUG: ObjectHeader claimed to be empty or already containing object

    id] 2 rbx 0x0000000101ec9f73 rubinius::bug(char const*) + 67 3 rbx 0x0000000101f8ec43 rubinius::ObjectHeader::set_object_id(rubinius::State*, rubinius::ObjectMemory*, 4 rbx 0x0000000101f8832d rubinius::ObjectMemory::assign_object_id(rubinius::State*, rubinius::Object*) + 1
  29. veritas $ ./gdbrunner

  30. Investigation

  31. bt (#nr) - Get the backtrace (#nr lines) frame #nr

    - Jump to frame #nr in backtrace p *obj - Inspect the object it points to p args - Look at the specific object p call_frame->print_backtrace(state, #nr) - Get Ruby backtrace (#nr lines) Cheat sheet <rubinius::ObjectHeader> = { header = { f = { ... Forwarded = 0, ... }, ... }, klass_ = 0x7f83fd58b6a0, ivars_ = 0x1a, __body__ = {0x0} Points to forward if Forwarded == 1 Arguments { name_ = 0xab6, recv_ = 0x102583c90, block_ = 0x1a, total_ = 1, arguments_ = 0x7fff5ee8e868, argument_container_ = 0x0 } Receiver # arguments p *args.arguments_[0]
  32. Manual auditing

  33. def all? if block_given? each { |*e| return false unless

    yield(*e) } else each { return false unless Rubinius.single_block_arg } end true end ./bin/rbx compile -B kernel/common/enumerable19.rb
  34. ================ :all? ================= Arguments: 0 required, 0 post, 0 total

    Arity: 0 Locals: 0 Stack size: 2 Line: 100 Lines to IP: 101: 0..2, 102: 3..11, 104: 12..18, 0: 19..19, 106: 20..21 0000: push_has_block 0001: goto_if_false 12 0003: push_self 0004: create_block #<Rubinius::CompiledMethod all? file=kernel/common/enumerable.rb> 0006: allow_private 0007: send_stack_with_block :each, 0 0010: goto 19 0012: push_self 0013: create_block #<Rubinius::CompiledMethod all? file=kernel/common/enumerable.rb> 0015: allow_private 0016: send_stack_with_block :each, 0 0019: pop 0020: push_true 0021: ret ----------------------------------------
  35. ================ :all? ================= Arguments: 0 required, 0 post, 0 total,

    (splat 0) Arity: -1 Locals: 1: e Stack size: 3 Line: 102 Lines to IP: 0: 16..16 0000: cast_for_splat_block_arg 0001: set_local 0 0003: pop 0004: push_local 0 0006: cast_array 0007: yield_splat 0 0009: goto_if_false 14 0011: push_nil 0012: goto 16 0014: push_false 0015: raise_return 0016: ret ---------------------------------------- ================ :all? ================= Arguments: 0 required, 0 post, 0 total, (splat -2) Arity: -1 Locals: 0 Stack size: 2 Line: 104 Lines to IP: 0: 8..8 0000: cast_for_single_block_arg 0001: goto_if_false 6 0003: push_nil 0004: goto 8 0006: push_false 0007: raise_return 0008: ret ----------------------------------------
  36. Object* Object* Object* C stack Heap 1 Heap 2 GC

  37. When?

  38. String* buffer = String::create_pinned(state, bytes); OnStack<1> variables(state, buffer); ssize_t bytes_read;

    native_int t = type->to_native(); retry: state->vm()->interrupt_with_signal(); state->vm()->thread->sleep(state, cTrue); { GCIndependent guard(state, calling_environment); bytes_read = recvfrom(descriptor()->to_native(), (char*)buffer->byte_address(), size, flags->to_native(), (struct sockaddr*)buf, &alen); } state->vm()->thread->sleep(state, cFalse); state->vm()->clear_waiter(); buffer->unpin();
  39. OnStack<1> os(state, obj);

  40. None
  41. instruction cast_for_splat_block_arg() [ -- arguments ] if(!call_frame->arguments) { Exception::internal_error(state, call_frame,

    "no arguments object"); RUN_EXCEPTION(); } if(call_frame->arguments->total() == 1) { Object* obj = call_frame->arguments->get_argument(0); if(!kind_of<Array>(obj)) { /* Yes, you are reading this code correctly: In Ruby 1.8, calling a * block with these forms { |*| } and { |*a| } with a single argument * that is not an Array and which responds to #to_ary will cause #to_ary * to be called and its return value ignored. Ultimately, the original * object itself is wrapped in an Array and passed to the block. */ if(CBOOL(obj->respond_to(state, state->symbol("to_ary"), cFalse))) { Object* ignored = obj->send(state, call_frame, state->symbol("to_ary")); if(!ignored->nil_p() && !kind_of<Array>(ignored)) { Exception::type_error(state, "to_ary must return an Array", call_frame); RUN_EXCEPTION(); } } } Array* ary = Array::create(state, 1); ary->set(state, 0, obj); stack_push(ary); } else { Array* ary = Array::create(state, call_frame->arguments->total()); for(size_t i = 0; i < call_frame->arguments->total(); i++) { ary->set(state, i, call_frame->arguments->get_argument(i)); } stack_push(ary); } end Method send! Object* on stack Object* reused!
  42. diff --git i/vm/instructions.cpp w/vm/instructions.cpp index 2c1b940..18b678a 100644 --- i/vm/instructions.cpp +++

    w/vm/instructions.cpp @@ -22,6 +22,7 @@ #include "builtin/cache.hpp" #include "call_frame.hpp" +#include "on_stack.hpp" #include "objectmemory.hpp" #include "arguments.hpp" diff --git i/vm/instructions.def w/vm/instructions.def index dbbc68e..65e6052 100644 --- i/vm/instructions.def +++ w/vm/instructions.def @@ -1290,6 +1290,7 @@ instruction cast_for_splat_block_arg() [ -- arguments ] * object itself is wrapped in an Array and passed to the block. */ if(CBOOL(obj->respond_to(state, state->symbol("to_ary"), cFalse))) { + OnStack<1> os(state, obj); Object* ignored = obj->send(state, call_frame, state->symbol("to_ary")); if(!ignored->nil_p() && !kind_of<Array>(ignored)) { Exception::type_error(state, "to_ary must return an Array", call_frame);
  43. None