เรา Rubyists รัก Hashes แต่ Hashes มีข้อบกพร่องที่รู้จักกันดี ตามที่ Richard 指出ใน Hashie Considered Harmful มันอาจ flexible เกินไป - typo เล็กน้อยก็ assign/reference keys ที่ไม่ได้ตั้งใจ
a = { type: "F150" } a[:typo] # nil
Hash Alternatives ปוצบบ
ถ้าใช้ hashes เก็บ structured data อาจตัดสินใจว่าไม่ต้องการ flexibility มันแค่ทำให้ปวดหัว
มีทางเลือกいくつか สมมติต้องเก็บ XY coordinates ทางหนึ่งคือ define class ที่ทำหน้าที่เก็บคู่ XY coordinates
class PointClass # ไม่แนะนำลงท้าย class ด้วย 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
ถ้าแค่ encapsulate data Struct เรียบง่ายกว่า:
PointStruct = Struct.new(:x, :y) point_struct = PointStruct.new(1, 2) point_struct.x # 1
ทางเลือกที่ 3 OpenStruct คล้าย Struct แต่ตั้งค่า arbitrary ได้เหมือน hash:
point_os = OpenStruct.new(x: 1, y: 2) point_os.x # 1
Performance Implications
[UPDATE 7/10/2015: Benchmark script ผม unfair กับ hashes ตาม Patrick Helm ชี้ให้เห็น ใช้วิธี initialize ไม่ efficient โปรด ignore hash results แต่จุดหลัก OpenStruct ช้ามากยังคงถูกต้อง ดูการเปลี่ยนแปลงของเขาที่นี่]
ดู 4 ทางเลือก ผมสงสัย performance implications ถ้า deal กับข้อมูลน้อย ทุกอย่างพอดี แต่ถ้า thousands/millions items performance ของ hash vs OpenStruct vs struct vs class เริ่มสำคัญ
ที่ Honeybadger มี thousands exceptions reported ต่อวินาที เข้าใจ performance อย่างนี้อยู่ในใจตลอด
เขียน benchmark script 簡單 ใช้ benchmark-ips gem เพราะ auto calculate sample size และ report standard deviation
Initialization
Benchmark initialization times ของ PointClass, PointStruct, Hash, OpenStruct พบว่า PointClass และ PointStruct ชนะชัดเจน ~10x เร็วกว่า OpenStruct และ ~2x เร็วกว่า hash

PointClass และ PointStruct เร็วกว่า OpenStruct เกือบ 10x
ผลลัพธ์สมเหตุสมผล Struct เรียบที่สุด เร็วสุด OpenStruct 複雜ที่สุด (wrapper ของ Hash) ช้าสุด แต่มากนิกของความต่างเร็วช้าค่อนข้าง surprising
หลัง experiment นี้ ผมจะหน่อยมากที่จะใช้ OpenStruct ใน code ที่สนใจ speed และจะมอง hashes ใน performance-critical code ด้วยตาสงสัย
Read / Write
ต่างจาก initialization ทั้ง 4 options เหมือนกันประมาณเวลา set และ access values

Reading/writing benchmarks ไม่มีความต่างใหญ่ระหว่าง Struct, class, hash และ OpenStruct
Benchmarking Script
อยากรัน benchmark เอง ใช้ script ด้านล่าง ผมรันบน MRI 2.1 on OSX สนใจ performance อื่นๆ Michael Cohen มี gist สำหรับ 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