Chào mừng bài Ruby Magic mới! Lần này xem Ruby interpret code như thế nào, và dùng knowledge này lợi ích. Bài giúp hiểu code interpret như thế nào, và dẫn đến code nhanh hơn.
Sự Khác Biệt Khó Nhận Giữa Symbols
Trong bài Ruby Magic trước về Escaping characters in Ruby, có ví dụ escaping line breaks.
Trong ví dụ dưới, thấy 2 string kết hợp thành một String qua multiple lines, với plus + symbol hoặc backslash \ .
"foo" + "bar" => "foobar" # versus "foo" \ "bar" => "foobar"Hai ví dụ có vẻ similar, nhưng behave khác nhau. Để biết khác biệt cách read/interpret, bạn thường cần nitty-gritty về Ruby interpreter. Hoặc, ta chỉ hỏi Ruby khác biệt là gì.
InstructionSequence
Dùng class RubyVM::InstructionSequence ta hỏi Ruby interpret code như thế nào. Class này cho tool set glimpse vào Ruby internals.
Return trong ví dụ dưới là Ruby code như YARV interpreter hiểu.
YARV Interpreter
YARV (Yet Another Ruby VM) là Ruby interpreter giới thiệu version 1.9, thay thế MRI (Matz's Ruby Interpreter) gốc.
Ngôn ngữ dùng interpreter trực tiếp execute code không intermediate compilation step. Nghĩa là Ruby không compile program thành optimized machine language program như compiled languages: C, Rust, Go.
Trong Ruby, program đầu translate thành instruction set cho Ruby VM, rồi execute ngay sau. Instructions là intermediate step giữa Ruby code và code execute trong Ruby VM.
Instructions giúp Ruby VM hiểu Ruby code dễ hơn, không deal với syntax-specific interpretation. Đó được handle khi tạo instructions. Instruction sequences là optimized operations represent interpreted code.
Trong normal execution Ruby program ta không thấy instructions, nhưng view chúng ta review Ruby interpret code đúng không. Với InstructionSequence có thể thấy YARV tạo instructions gì trước execute.
Không cần hiểu tất cả YARV instructions tạo nên Ruby interpreter. Hầu hết commands tự nói lên mình.
"foo" + "bar" RubyVM::InstructionSequence.compile('"foo" + "bar"').to_a # ... [:putstring, "foo"], [:putstring, "bar"] ... # versus "foo" \ "bar" RubyVM::InstructionSequence.compile('"foo" "bar"').to_a # ... [:putstring, "foobar"] ...Real output chứa thêm setup commands xem sau, nhưng đây thấy real difference giữa "foo" + "bar" và "foo" "bar".
Cái trước tạo 2 strings và combine. Cái sau tạo 1 string. Nghĩa là với "foo" "bar" ta chỉ tạo 1 string, thay vì 3 với "foo" + "bar".
1 2 3 ↓ ↓ ↓ "foo" + "bar" # => "foobar" Đương nhiên, đây chỉ basic example, nhưng show good use case small detail Ruby language có thể impact lớn:
- More allocations: mỗi String object allocate riêng.
- More memory usage: mỗi allocated String object tốn memory.
- Longer garbage collection: mỗi object, dù short-lived, tốn time cleaned by GC. More allocations nghĩa là longer GC times.
Disassembling
Use case khác: debug logic issue. Ví dụ dễ mắc lỗi, hậu quả lớn. Bạn spot difference?
1 + 2 * 3 # versus (1 + 2) * 3Ta dùng Ruby tìm difference trong slightly more complex example này.
Bằng cách disassemble code example, ta nhờ Ruby print readable table commands nó perform.
1 + 2 * 3 # => 7 puts RubyVM::InstructionSequence.compile("1 + 2 * 3").disasm # == disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>========== # 0000 trace 1 ( 1) # 0002 putobject_OP_INT2FIX_O_1_C_ # 0003 putobject 2 # 0005 putobject 3 # 0007 opt_mult <callinfo!mid:*, argc:1, ARGS_SIMPLE> # 0009 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE> # 0011 leave # versus (1 + 2) * 3 # => 9 puts RubyVM::InstructionSequence.compile("(1 + 2) * 3").disasm # == disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>========== # 0000 trace 1 ( 1) # 0002 putobject_OP_INT2FIX_O_1_C_ # 0003 putobject 2 # 0005 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE> # 0007 putobject 3 # 0009 opt_mult <callinfo!mid:*, argc:1, ARGS_SIMPLE> # 0011 leaveVí dụ trên involved hơn với số YARV instructions, nhưng chỉ từ order things printed/executed thấy difference cặp parentheses làm.
Với parentheses quanh 1 + 2 ta đảm bảo addition perform first, bằng cách move nó lên order of operations in mathematics.
Lưu ý bạn không thấy parentheses trong disassembly output, chỉ thấy effect của chúng lên code còn lại.
Disassembly
Disassembly output in nhiều thứ không ngay lập tức understandable.
Trong table format in ra, mỗi dòng bắt đầu operation number. Sau đó mention operation và finally argument to operation.
Sample nhỏ operations thấy:
trace- start trace. Xem docs TracePoint để info thêm.putobject- push object lên stack.putobject_OP_INT2FIX_O_1_C_- push Integer1lên stack. Optimized operation. (0và1optimized.)putstring- push string lên stack.opt_plus- addition operation (internally optimized).opt_mult- multiply operation (internally optimized).leave- leave current code context.
Bây giờ biết Ruby interpreter convert developer-friendly Ruby code thành YARV instructions, ta dùng optimize applications.
Có thể pass entire methods thậm chí entire files cho RubyVM::InstructionSequence.
puts RubyVM::InstructionSequence.disasm(method(:foo)) puts RubyVM::InstructionSequence.compile_file("/tmp/hello.rb").disasmTìm ra tại sao code hoạt động và cái khác không. Học tại sao certain symbols làm code behave khác. Devil in the details, tốt biết Ruby code behave như thế nào trong app và có optimize không.
Optimizations
Ngoài view code interpreter level optimize, ta dùng InstructionSequence optimize code hơn nữa.
Với InstructionSequence, có thể optimize certain instructions với built-in performance optimizations Ruby. Full list available optimizations trong docs RubyVM::InstructionSequence.compile_option = method.
Một optimization là Tail Call Optimization.
Method RubyVM::InstructionSequence.compile accept options enable optimization:
some_code = <<-EOS def fact(n, acc=1) return acc if n <= 1 fact(n-1, n*acc) end EOS puts RubyVM::InstructionSequence.compile(some_code, nil, nil, nil, tailcall_optimization: true, trace_instruction: false).disasm RubyVM::InstructionSequence.compile(some_code, nil, nil, nil, tailcall_optimization: true, trace_instruction: false).evalThậm chí bật optimization cho all code với RubyVM::InstructionSequence.compile_option =. Chỉ load trước any other code.
RubyVM::InstructionSequence.compile_option = { tailcall_optimization: true, trace_instruction: false }Thêm info về Tail Call Optimization trong Ruby xem bài: Tail Call Optimization in Ruby và Tail Call Optimization in Ruby: Background.