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

OpenStruct สามารถฆ่าประสิทธิภาพได้อย่างไร

พวกเรา Rubyists ชอบแฮชของเรา แต่แฮชก็มีข้อบกพร่องที่รู้จักกันดีอยู่บ้าง ดังที่ Richard ระบุไว้ใน Hashie ที่ถือว่าเป็นอันตราย บางครั้งอาจมีความยืดหยุ่นเกินไป - พิมพ์ผิดอย่างรวดเร็ว และคุณสามารถกำหนดและอ้างอิงคีย์ที่คุณไม่ได้ตั้งใจได้

a = { type: "F150" }
a[:typo] # nil

ทางเลือกแฮชทั่วไปบางรายการ

หากคุณกำลังใช้แฮชเพื่อจัดเก็บข้อมูลที่มีโครงสร้างจริง คุณอาจตัดสินใจว่าคุณไม่จำเป็นต้องมีความยืดหยุ่น ที่มันจะทำให้คุณเดือดร้อนเท่านั้น

มีทางเลือกสองสามทาง ลองนึกภาพว่าคุณต้องเก็บพิกัด XY ไว้คู่หนึ่ง วิธีหนึ่งอาจเป็นการกำหนดคลาส ซึ่งงานเดียวคือเก็บพิกัด XY ไว้คู่หนึ่ง

class PointClass # I don't recommend ending class names with Class :) 
  attr_accessor :x, :y
  def initialize(args)
    @x = args.fetch(:x)
    @y = args.fetch(:y)
  end
end

point_class = PointClass.new(x: 1, y: 2)
point_class.x # 1

เนื่องจากในกรณีนี้ เราจำเป็นต้องแค็ปซูลข้อมูลเท่านั้น ทางเลือกที่กระชับกว่านี้อาจเป็นการใช้โครงสร้าง นี่คือลักษณะ:

PointStruct = Struct.new(:x, :y)

point_struct = PointStruct.new(1, 2)
point_struct.x # 1

ตัวเลือกที่สามอาจเป็นการใช้ OpenStruct OpenStruct ดูเหมือนโครงสร้าง แต่ให้คุณตั้งค่าตามอำเภอใจเช่นแฮช นี่คือตัวอย่าง:

point_os = OpenStruct.new(x: 1, y: 2)

point_os.x # 1

ผลกระทบด้านประสิทธิภาพ

[อัพเดท 7/10/2015:ดูเหมือนว่าสคริปต์การเปรียบเทียบของฉันไม่ยุติธรรมที่จะแฮช ตามที่ Patrick Helm ชี้ให้เห็น ฉันกำลังใช้วิธีการเริ่มต้นที่ไม่มีประสิทธิภาพ ดังนั้นโปรดเพิกเฉยต่อผลลัพธ์ของแฮช แม้ว่าประเด็นหลักของฉันเกี่ยวกับ openstruct ที่ช้ามากก็ยังใช้ได้ คุณสามารถดูการเปลี่ยนแปลงของเขาในสคริปต์เปรียบเทียบของฉันได้ที่นี่]

เมื่อพิจารณาจากสี่ตัวเลือกนี้ ฉันเริ่มสงสัยว่าผลการปฏิบัติงานคืออะไร เห็นได้ชัดว่าตัวเลือกใด ๆ เหล่านี้เร็วพอหากคุณจัดการกับข้อมูลเพียงเล็กน้อย แต่หากคุณมีรายการเป็นพันหรือล้านรายการที่ต้องดำเนินการ ผลกระทบด้านประสิทธิภาพของแฮชกับ OpenStruct กับ struct กับคลาสอาจเริ่มมีความสำคัญ

ที่ Honeybadger เรามีข้อยกเว้นหลายพันรายการที่รายงานไปยัง API ของเราทุกวินาที ดังนั้นการทำความเข้าใจเกี่ยวกับผลกระทบด้านประสิทธิภาพเช่นนี้จึงอยู่ในใจของเราเสมอ

ดังนั้นฉันจึงเขียนสคริปต์เบนช์มาร์กอย่างง่าย ฉันชอบใช้เบนช์มาร์ก-ips gem สำหรับการทดลองเช่นนี้ เพราะมันคำนวณขนาดตัวอย่างที่ดีและรายงานค่าเบี่ยงเบนมาตรฐานโดยอัตโนมัติ

การเริ่มต้น

เมื่อฉันเปรียบเทียบเวลาเริ่มต้นสำหรับ PointClass, PointStruct, Hash และ OpenStruct ฉันพบว่า PointClass และ PointStruct เป็นผู้ชนะที่ชัดเจน เร็วกว่า OpenStruct ประมาณ 10 เท่า และเร็วกว่าแฮชประมาณ 2 เท่า

OpenStruct สามารถฆ่าประสิทธิภาพได้อย่างไร

OpenStruct สามารถฆ่าประสิทธิภาพได้อย่างไร PointClass และ PointStruct เร็วกว่า OpenStruct เกือบ 10 เท่า

ผลลัพธ์เหล่านี้สมเหตุสมผล โครงสร้างนั้นง่ายที่สุด ดังนั้นพวกมันจึงเร็วที่สุด OpenStruct นั้นซับซ้อนที่สุด (เป็น wrapper สำหรับ Hash) ดังนั้นจึงช้าที่สุด อย่างไรก็ตาม ความต่างของความเร็วนั้นค่อนข้างน่าประหลาดใจ

หลังจากทำการทดลองนี้ ฉันลังเลจริงๆ ที่จะใช้ OpenStruct ในโค้ดใดๆ ก็ตามที่เกี่ยวข้องกับความเร็ว และฉันจะคอยจับตาดูแฮชต่างๆ ที่ฉันเห็นในโค้ดที่มีความสำคัญต่อประสิทธิภาพ

อ่าน / เขียน

ต่างจากการเริ่มต้นตรง ตัวเลือกทั้ง 4 ตัวจะใกล้เคียงกันเมื่อต้องตั้งค่าและเข้าถึงค่า

OpenStruct สามารถฆ่าประสิทธิภาพได้อย่างไร

OpenStruct สามารถฆ่าประสิทธิภาพได้อย่างไร เกณฑ์มาตรฐานการอ่านและการเขียนไม่แตกต่างกันมากระหว่าง Struct, class, hash และ OpenStruct

สคริปต์การเปรียบเทียบ

หากคุณต้องการเรียกใช้เบนช์มาร์กบนระบบของคุณเอง คุณสามารถใช้สคริปต์ด้านล่าง ฉันรันบน MRI 2.1 บน OSX หากคุณอยากรู้เกี่ยวกับประสิทธิภาพของล่ามทับทิมอื่นๆ Michael Cohen ได้สร้างส่วนสำคัญที่ยอดเยี่ยมพร้อมผลลัพธ์สำหรับ MRI 2.2, JRuby และอื่นๆ

require 'benchmark/ips'
require 'ostruct'

data = { x: 100, y: 200 }

PointStruct = Struct.new(:x, :y)

class PointClass
  attr_accessor :x, :y
  def initialize(args)
    @x = args.fetch(:x)
    @y = args.fetch(:y)
  end
end

puts "\n\nINITIALIZATION =========="

Benchmark.ips do |x|
  x.report("PointStruct") { PointStruct.new(100, 200) }
  x.report("PointClass") { PointClass.new(data) }
  x.report("Hash") { Hash.new.merge(data) }
  x.report("OpenStruct") { OpenStruct.new(data) }
end

puts "\n\nREAD =========="

point_struct = PointStruct.new(100, 200)
point_class = PointClass.new(data)
point_hash = Hash.new.merge(data)
point_open_struct = OpenStruct.new(data)

Benchmark.ips do |x|
  x.report("PointStruct") { point_struct.x }
  x.report("PointClass") {  point_class.x }
  x.report("Hash") { point_hash.fetch(:x) }
  x.report("OpenStruct") {  point_open_struct.x }
end


puts "\n\nWRITE =========="

Benchmark.ips do |x|
  x.report("PointStruct") { point_struct.x = 1 }
  x.report("PointClass") {  point_class.x = 1 }
  x.report("Hash") { point_hash[:x] = 1 }
  x.report("OpenStruct") {  point_open_struct.x = 1 }
end