Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Ruby Performance: Benchmarking โค้ดอย่างมีประสิทธิภาพ

Ruby มี benchmarking tool ใน standard library ช่วยวัดประสิทธิภาพโค้ด มีประโยชน์ที่สุดเมื่อเปรียบเทียบสอง implementations เพื่อหาว่าตัวไหนเร็วกว่า

ในตัวอย่างนี้ เราได้รับมอบหมายให้แปลง Hash ที่มี string keys (เช่น {"foo" => "bar"}) เป็น symbol keys (เช่น {:foo => "bar"}) ตลอดตัวอย่าง เราจะใช้ hash ที่มี key และ value สำหรับตัวอักษรทุกตัวในอักษรอังกฤษ

เพื่อ generate hash นี้เร็วโดยไม่ต้องพิมพ์เอง เราจะแปลง range ของตัวอักษรเป็น testing hash ใส่ในตัวแปร input ใช้ต่อ

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

ตอนนี้เรามี input variable ทดสอบ implementations แล้ว เราจะเขียนหนึ่งตัวดูประสิทธิภาพ one-liner สำหรับแปลง keys ทั้งหมดใน input hash เป็น symbols แทน strings เป็นแบบนี้:

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 นี้ใช้ map method วนลูป hash รัน block สำหรับแต่ละ key-value pair ใน block แปลง key เป็น symbol คืน array 2 elements พร้อม symbol key ใหม่ และ value เดิม

ผลลัพธ์จาก map command คือ array พร้อม 26 key-value arrays เนื่องจากต้องการ hash เราใช้ #to_h แปลง array ใหม่กลับเป็น hash

Benchmark.measure

ตอนนี้เรามี working implementation ใช้ Ruby's Benchmark module ดูประสิทธิภาพ

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 รับ block execute พร้อม track ใช้เวลานานแค่ไหน คืน report string พิมพ์ไป console ด้วย puts

เนื่องจากโค้ดเร็ว เรารัน 50,000 ครั้ง เพื่อให้ได้ visible results

$ ruby bench.rb
0.810000 0.000000 0.810000 ( 0.816964)

Report string แสดง 4 ตัวเลข แทน user CPU time (เวลา execute โค้ดของคุณ) system CPU time (เวลาใน kernel) user และ system CPU time รวมกัน และ actual time (wall clock time) ที่ block ใช้ execute ในวงเล็บ

Wall time แสดงว่าเรารัน block โค้ดข้างต้น 50,000 ครั้งในประมาณ 800 milliseconds แม้ impressive แต่เราไม่รู้ความหมายจนกว่าเปรียบเทียบกับ implementation อื่น

Benchmark.bm

นอกจาก Benchmark.measure Ruby มี Benchmark.bm รัน multiple code samples พิมพ์ผลลัพธ์ สำหรับแต่ละ sample เราเรียก Benchmark#report พร้อมชื่อ และ 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

ใน benchmark นี้ เราใช้ Benchmark.bm ทดสอบ 2 implementations รันแต่ละ 50,000 ครั้ง Measurement block แรกเหมือนตัวอย่างก่อนหน้า

ใน measurement block ที่สอง เราใช้ implementation ยาวกว่า สร้าง hash ใหม่ล่วงหน้า วนลูป string-key hash เพิ่ม element ให้ new hash ทุก item วิธีนี้ไม่ต้องแปลง hash เป็น array และกลับเป็น hash เมื่อเสร็จ

รัน benchmark อีกครั้งจะแสดง implementation นี้เร็วกว่า 25% แม้โค้ดยาวกว่า (และ clever น้อยกว่า) one-liner ที่ลองก่อนหน้า

$ 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 เพิ่มเติม

เมื่อทำงานบน important piece of code ใน codebase การรัน benchmarks เปรียบเทียบ implementations ต่างๆ ให้ insight มากขึ้นเกี่ยว execution speeds ด้วยการเปรียบเทียบ implementations เพื่อเข้าใจ impact ต่อประสิทธิภาพ คุณจะหลีกเลี่ยง anti-patterns และเขียน Ruby เร็วขึ้น

Tip: common idioms หลายอย่าง pre-benchmarked แล้ว ผลลัพธ์เผยแพร่เป็น fast-ruby การอ่านตัวอย่างอาจประหยัด benchmarking บ้างในอนาคต

มี options เพิ่มเติมทดสอบสำหรับตัวอย่างนี้ และ Ruby's benchmarking library มี sophisticated features มากกว่านี้ลองได้ แต่นี่เป็น introduction ดีเกี่ยว benchmarking ใน Ruby หากอยากรู้เพิ่มเกี่ยว benchmarking หรือมีคำถาม/ข้อเสนอแนะ บอกเราที่ @AppSignal ได้เลย