Computer >> Máy Tính >  >> Lập trình >> Ruby

Cách OpenStruct có thể giết chết hiệu suất

Những người theo chủ nghĩa Ruby chúng tôi yêu thích hàm băm của chúng tôi. Nhưng băm có một vài sai sót nổi tiếng. Như Richard đã chỉ ra trong Hashie được coi là có hại, chúng có thể QUÁ linh hoạt - một lỗi đánh máy nhanh và bạn có thể gán và tham chiếu các khóa mà bạn không bao giờ có ý định.

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

Một số lựa chọn thay thế băm phổ biến

Nếu bạn đang sử dụng hàm băm để lưu trữ dữ liệu có cấu trúc thực tế, bạn có thể quyết định rằng bạn không thực sự cần sự linh hoạt. Điều đó chỉ khiến bạn gặp rắc rối.

Có một số lựa chọn thay thế. Hãy tưởng tượng rằng bạn cần lưu trữ một cặp tọa độ XY. Một cách tiếp cận có thể là xác định một lớp, công việc duy nhất của nó là giữ một cặp tọa độ 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

Vì trong trường hợp này chúng ta chỉ cần đóng gói dữ liệu, nên một lựa chọn ngắn gọn hơn có thể là sử dụng Struct. Đây là những gì trông giống như:

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

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

Tùy chọn thứ ba có thể là sử dụng OpenStruct. OpenStruct trông giống như một cấu trúc, nhưng cho phép bạn đặt các giá trị tùy ý như một hàm băm. Đây là một ví dụ:

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

point_os.x # 1

Các hàm ý về Hiệu suất

[CẬP NHẬT 7/10/2015:Có vẻ như tập lệnh đo điểm chuẩn của tôi không công bằng đối với hàm băm. Như Patrick Helm đã chỉ ra, tôi đang sử dụng một phương pháp khởi tạo chúng không hiệu quả. Vì vậy, xin vui lòng bỏ qua kết quả cho băm. Mặc dù quan điểm chính của tôi về việc cấu trúc mở siêu chậm vẫn còn giá trị. Bạn có thể xem các thay đổi của anh ấy đối với tập lệnh điểm chuẩn của tôi tại đây]

Nhìn vào bốn lựa chọn này, tôi bắt đầu tự hỏi tác động của hiệu suất là gì. Rõ ràng là bất kỳ tùy chọn nào trong số này đều đủ nhanh nếu bạn chỉ xử lý một ít dữ liệu. Nhưng nếu bạn có hàng nghìn hoặc hàng triệu mục cần xử lý, thì tác động hiệu suất của băm so với OpenStruct so với struct và class có thể bắt đầu quan trọng.

Tại Honeybadger, chúng tôi có hàng nghìn trường hợp ngoại lệ được báo cáo cho API của chúng tôi mỗi giây, vì vậy việc hiểu các hàm ý về hiệu suất như thế này luôn trong tâm trí chúng tôi.

Vì vậy, tôi đã viết một kịch bản điểm chuẩn đơn giản. Tôi thích sử dụng đá quý benchmark-ips cho các thử nghiệm như thế này vì nó tự động tìm ra kích thước mẫu phù hợp và báo cáo độ lệch chuẩn.

Khởi tạo

Khi tôi chuẩn hóa thời gian khởi tạo cho PointClass, PointStruct, Hash và OpenStruct, tôi thấy rằng PointClass và PointStruct là những người chiến thắng rõ ràng. Chúng nhanh hơn khoảng 10 lần so với OpenStruct và nhanh hơn khoảng 2 lần so với hàm băm.

Cách OpenStruct có thể giết chết hiệu suất

Cách OpenStruct có thể giết chết hiệu suất PointClass và PointStruct nhanh hơn OpenStruct gần 10 lần

Những kết quả này có ý nghĩa. Các cấu trúc là đơn giản nhất, vì vậy chúng nhanh nhất. OpenStruct là phức tạp nhất (nó là một trình bao bọc cho Hash) vì vậy nó là chậm nhất. Tuy nhiên, mức độ khác biệt về tốc độ thật đáng ngạc nhiên.

Sau khi chạy thử nghiệm này, tôi thực sự do dự khi sử dụng OpenStruct trong bất kỳ mã nào mà tốc độ là mối quan tâm. Và tôi sẽ cảnh giác với bất kỳ hàm băm nào mà tôi thấy trong mã quan trọng về hiệu suất.

Đọc / Ghi

Không giống như khởi tạo, tất cả bốn tùy chọn gần như giống nhau khi nói đến thiết lập và truy cập các giá trị.

Cách OpenStruct có thể giết chết hiệu suất

Cách OpenStruct có thể giết chết hiệu suất Điểm chuẩn đọc và ghi không có sự khác biệt lớn giữa Struct, class, hash và OpenStruct

Tập lệnh đo điểm chuẩn

Nếu bạn muốn chạy điểm chuẩn trên hệ thống của riêng mình, bạn có thể sử dụng tập lệnh bên dưới. Tôi đã chạy nó trên MRI 2.1 trên OSX. Nếu bạn tò mò về hiệu suất trên các trình thông dịch ruby ​​khác, Michael Cohen đã tạo ra một ý chính tuyệt vời với các kết quả cho MRI 2.2, JRuby và những người khác.

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