พวกเรา 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 เท่า
PointClass และ PointStruct เร็วกว่า OpenStruct เกือบ 10 เท่า
ผลลัพธ์เหล่านี้สมเหตุสมผล โครงสร้างนั้นง่ายที่สุด ดังนั้นพวกมันจึงเร็วที่สุด OpenStruct นั้นซับซ้อนที่สุด (เป็น wrapper สำหรับ Hash) ดังนั้นจึงช้าที่สุด อย่างไรก็ตาม ความต่างของความเร็วนั้นค่อนข้างน่าประหลาดใจ
หลังจากทำการทดลองนี้ ฉันลังเลจริงๆ ที่จะใช้ OpenStruct ในโค้ดใดๆ ก็ตามที่เกี่ยวข้องกับความเร็ว และฉันจะคอยจับตาดูแฮชต่างๆ ที่ฉันเห็นในโค้ดที่มีความสำคัญต่อประสิทธิภาพ
อ่าน / เขียน
ต่างจากการเริ่มต้นตรง ตัวเลือกทั้ง 4 ตัวจะใกล้เคียงกันเมื่อต้องตั้งค่าและเข้าถึงค่า
เกณฑ์มาตรฐานการอ่านและการเขียนไม่แตกต่างกันมากระหว่าง 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