$30 off During Our Annual Pro Sale. View Details »

Rubinius Eurucamp 2012 Workshop

Rubinius Eurucamp 2012 Workshop

Slides for the Rubinius workshop given at Eurucamp 2012

Dirkjan Bussink

August 17, 2012
Tweet

More Decks by Dirkjan Bussink

Other Decks in Programming

Transcript

  1. Rubinius
    Use Ruby

    View Slide

  2. Dirkjan Bussink
    [email protected]

    View Slide

  3. View Slide

  4. Workshop
    Hacking on Rubinius

    View Slide

  5. Performance
    Fix a GC crash

    View Slide

  6. View Slide

  7. Benchmarking

    View Slide

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

    View Slide

  9. What is off?

    View Slide

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

    View Slide

  11. Profiling

    View Slide

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

    View Slide

  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> {}

    View Slide

  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> {}

    View Slide

  15. Structure

    View Slide

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

    View Slide

  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

    View Slide

  18. Why is it slower?

    View Slide

  19. Implement a fix

    View Slide

  20. View Slide

  21. View Slide

  22. Reproduction

    View Slide

  23. View Slide

  24. veritas $ until [ $? != 0 ];
    do ../rubinius/bin/rbx -Xint -S rake spec:unit; done

    View Slide

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

    View Slide

  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);
    }
    }

    View Slide

  27. rubinius $ rake clean
    rubinius $ DEV=1 rake build

    View Slide

  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

    View Slide

  29. veritas $ ./gdbrunner

    View Slide

  30. Investigation

    View Slide

  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
    = {
    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]

    View Slide

  32. Manual auditing

    View Slide

  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

    View Slide

  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 #
    0006: allow_private
    0007: send_stack_with_block :each, 0
    0010: goto 19
    0012: push_self
    0013: create_block #
    0015: allow_private
    0016: send_stack_with_block :each, 0
    0019: pop
    0020: push_true
    0021: ret
    ----------------------------------------

    View Slide

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

    View Slide

  36. Object*
    Object*
    Object*
    C stack
    Heap 1
    Heap 2 GC

    View Slide

  37. When?

    View Slide

  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();

    View Slide

  39. OnStack<1> os(state, obj);

    View Slide

  40. View Slide

  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(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(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!

    View Slide

  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(ignored)) {
    Exception::type_error(state, "to_ary must return an Array", call_frame);

    View Slide

  43. View Slide