Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Thành Thạo Ruby Performance: Benchmark Code Hiệu Quả

Ruby có công cụ benchmarking trong standard library giúp đo hiệu suất code. Nó hữu ích nhất khi so sánh hai implementation, tìm ra cái nào nhanh nhất.

Trong ví dụ này, ta được task chuyển đổi Hash với string keys (như {"foo" => "bar"} sang symbol keys (như {:foo => "bar"}). Khắp ví dụ, ta dùng hash với key và value cho mỗi chữ cái alphabet tiếng Anh.

Để nhanh chóng generate hash này mà không gõ tay, ta convert range chữ cái thành testing hash. Đặt vào biến input để dùng sau.

input = ("a".."z").map {|letter| [letter, letter]}.to_h # => {"a"=>"a", "b"=>"b", "c"=>"c", "d"=>"d", "e"=>"e", "f"=>"f", "g"=>"g", "h"=>"h", "i"=>"i", "j"=>"j", "k"=>"k", "l"=>"l", "m"=>"m", "n"=>"n", "o"=>"o", "p"=>"p", "q"=>"q", "r"=>"r", "s"=>"s", "t"=>"t", "u"=>"u", "v"=>"v", "w"=>"w", "x"=>"x", "y"=>"y", "z"=>"z"}

Bây giờ có biến input để test implementation, ta viết một để xem hiệu suất. One-liner đẹp để convert tất cả keys trong input hash sang symbol thay vì string:

input.map { |key, value| [key.to_sym, value] }.to_h # => {:a=>"a", :b=>"b", :c=>"c", :d=>"d", :e=>"e", :f=>"f", :g=>"g", :h=>"h", :i=>"i", :j=>"j", :k=>"k", :l=>"l", :m=>"m", :n=>"n", :o=>"o", :p=>"p", :q=>"q", :r=>"r", :s=>"s", :t=>"t", :u=>"u", :v=>"v", :w=>"w", :x=>"x", :y=>"y", :z=>"z"}

Implementation này dùng method map lặp qua hash chạy block cho mỗi key-value pair. Trong block, convert key thành symbol và return array 2 phần tử với symbol key mới, và value nguyên vẹn.

Kết quả từ map là array 26 key-value arrays. Vì cần hash, dùng #to_h convert array mới về hash.

Benchmark.measure

Bây giờ có working implementation, ta dùng module Benchmark của Ruby xem hiệu suất.

require 'benchmark' input = ('a'..'z').map { |letter| [letter, letter] }.to_h puts Benchmark.measure { 50_000.times do input.map { |key, value| [key.to_sym, value] }.to_h end }

Benchmark.measure nhận block, execute theo dõi thời gian thực thi. Trả về report string, in ra console bằng puts.

Vì code nhanh, chạy 50.000 lần để có visible results.

$ ruby bench.rb 0.810000 0.000000 0.810000 ( 0.816964) 

Report string show 4 số: user CPU time (thời gian execute code), system CPU time (thời gian trong kernel), tổng user+system CPU time, và actual time (wall clock time) block execute trong ngoặc.

Wall time cho thấy ta chạy block code trên 50.000 lần trong ~800ms. Mặc dù ấn tượng, ta không biết nghĩa gì trừ khi so sánh implementation khác.

Benchmark.bm

Ngoài Benchmark.measure, Ruby cung cấp Benchmark.bm, chạy multiple code samples và in kết quả. Với mỗi sample, gọi Benchmark#report với tên và block execute.

require 'benchmark' input = ("a".."z").map { |letter| [letter, letter] }.to_h n = 50_000 Benchmark.bm do |benchmark| benchmark.report("Hash[]") do n.times do input.map { |key, value| [key.to_sym, value] }.to_h end end benchmark.report("{}.tap") do n.times do {}.tap do |new_hash| input.each do |key, value| new_hash[key.to_sym] = value end end end end end

Trong benchmark này, ta dùng Benchmark.bm test 2 implementation, chạy mỗi 50.000 lần. Block measurement đầu giống ví dụ trước.

Trong block measurement thứ 2, ta dùng implementation dài hơn, tạo new hash trước. Lặp qua string-key hash, thêm element vào new hash cho mỗi item. Cách này không cần convert hash thành array, rồi ngược lại hash khi xong.

Chạy benchmark lại show implementation này nhanh hơn >25%, dù code dài hơn (và hơi kém "clever") hơn one-liner trước.

$ ruby bench.rb user system total real Hash[] 0.850000 0.000000 0.850000 ( 0.851106) {}.tap 0.610000 0.020000 0.630000 ( 0.637070) 

Benchmarking Thêm

Khi làm việc trên đoạn code quan trọng trong codebase, chạy benchmark so sánh implementation khác nhau cho insight về execution speed. Bằng cách so sánh implementation hiểu impact lên performance, bạn tránh anti-patterns và viết Ruby nhanh hơn.

Mẹo: Nhiều common idioms pre-benchmarked, kết quả published as fast-ruby. Đọc ví dụ có thể tiết kiệm benchmarking sau này.

Còn nhiều option test cho ví dụ này, và Ruby benchmarking library có nhiều sophisticated features hơn, nhưng đây là intro tốt về benchmarking trong Ruby. Nếu muốn biết thêm về benchmarking, hoặc có câu hỏi/gợi ý, liên hệ @AppSignal.