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

เมื่อใดควรใช้การแช่แข็งและแช่แข็ง ในทับทิม

ทุกวันนี้เป็นเรื่องปกติที่จะเห็น #freeze ใช้ในรหัสทับทิม แต่มักไม่ชัดเจนว่าทำไมจึงมีการใช้การแช่แข็ง ในบทความนี้ เราจะพิจารณาสาเหตุที่พบบ่อยที่สุดที่นักพัฒนาซอฟต์แวร์อาจหยุดการทำงานของตัวแปร เพื่อแสดงเหตุผลแต่ละข้อ ฉันได้คัดลอกโค้ดตัวอย่างจาก Codebase ของ Rails และโครงการโอเพนซอร์สยอดนิยมอื่นๆ

การสร้างค่าคงที่ที่ไม่เปลี่ยนรูป

ใน Ruby ค่าคงที่สามารถเปลี่ยนแปลงได้ มันค่อนข้างสับสน แต่รหัสนั้นเข้าใจง่ายพอที่จะเข้าใจ ที่นี่ ฉันได้สร้างค่าคงที่สตริงและต่อท้ายสตริงอื่นเข้าไป

MY_CONSTANT = "foo"
MY_CONSTANT << "bar"
puts MY_CONSTANT.inspect # => "foobar"

ด้วยการใช้ #freeze ฉันสามารถสร้างค่าคงที่ที่เป็นค่าคงที่จริงๆ ได้ ครั้งนี้ เมื่อฉันพยายามแก้ไขสตริง ฉันจะได้รับ RuntimeError

MY_CONSTANT = "foo".freeze
MY_CONSTANT << "bar" # => RuntimeError: can't modify frozen string

นี่คือตัวอย่างในโลกแห่งความเป็นจริงในฐานรหัส ActionDispatch Rails ซ่อนข้อมูลที่ละเอียดอ่อนในบันทึกโดยแทนที่ด้วยข้อความ "[FILTERED]" ข้อความนี้ถูกเก็บไว้ในค่าคงที่แช่แข็ง

module ActionDispatch
  module Http
    class ParameterFilter
      FILTERED = '[FILTERED]'.freeze
      ...

การลดการจัดสรรออบเจ็กต์

หนึ่งในสิ่งที่ดีที่สุดที่คุณสามารถทำได้เพื่อเพิ่มความเร็วของแอป Ruby คือการลดจำนวนวัตถุที่สร้างขึ้น แหล่งที่มาของการจัดสรรออบเจ็กต์ที่น่ารำคาญอย่างหนึ่งมาจากตัวอักษรสตริงที่กระจายไปทั่วแอปส่วนใหญ่

ทุกครั้งที่คุณเรียกใช้เมธอด เช่น log("foobar") คุณจะสร้างอ็อบเจ็กต์สตริงใหม่ หากโค้ดของคุณเรียกใช้เมธอดเช่นนี้หลายพันครั้งต่อวินาที แสดงว่าคุณกำลังสร้าง (และรวบรวมขยะ) หลายพันสตริงต่อวินาที มีค่าใช้จ่ายเยอะมาก!

โชคดีที่รูบี้ให้ทางออกแก่เรา หากเราตรึงตัวอักษรสตริง ตัวแปล Ruby จะสร้างออบเจ็กต์ String เพียงตัวเดียวและจะแคชไว้ใช้ในอนาคต ฉันได้รวบรวมเกณฑ์มาตรฐานด่วนที่แสดงประสิทธิภาพของอาร์กิวเมนต์สตริงที่ตรึงและไม่มีการตรึงไว้ แสดงให้เห็นประสิทธิภาพเพิ่มขึ้นประมาณ 50%

require 'benchmark/ips'

def noop(arg)
end

Benchmark.ips do |x|
  x.report("normal") { noop("foo") }
  x.report("frozen") { noop("foo".freeze)  }
end

# Results with MRI 2.2.2:
# Calculating -------------------------------------
#               normal   152.123k i/100ms
#               frozen   167.474k i/100ms
# -------------------------------------------------
#               normal      6.158M (± 3.3%) i/s -     30.881M
#               frozen      9.312M (± 3.5%) i/s -     46.558M

คุณสามารถเห็นสิ่งนี้ได้หากคุณดูที่เราเตอร์ Rails เนื่องจากเราเตอร์ใช้สำหรับคำขอหน้าเว็บทุกครั้ง เราเตอร์จึงต้องมีความรวดเร็ว นั่นหมายถึงตัวอักษรสตริงที่ถูกตรึงจำนวนมาก

# excerpted from https://github.com/rails/rails/blob/f91439d848b305a9d8f83c10905e5012180ffa28/actionpack/lib/action_dispatch/journey/router/utils.rb#L15
def self.normalize_path(path)
  path = "/#{path}"
  path.squeeze!('/'.freeze)
  path.sub!(%r{/+\Z}, ''.freeze)
  path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
  path = '/' if path == ''.freeze
  path
end

การเพิ่มประสิทธิภาพในตัวใน Ruby>=2.2

Ruby 2.2 และใหม่กว่า (MRI) จะตรึงตัวอักษรสตริงที่ใช้เป็นคีย์แฮชโดยอัตโนมัติ

user = {"name" => "george"}

# In Ruby >= 2.2
user["name"]

# ...is equivalent to this, in Ruby <= 2.1
user["name".freeze]

และตามข้อมูลของ Matz อักษรสตริงทั้งหมดจะถูกตรึงโดยอัตโนมัติใน Ruby 3

ค่าอ็อบเจ็กต์ &ฟังก์ชันการเขียนโปรแกรม

แม้ว่า Ruby จะไม่ใช่ภาษาการเขียนโปรแกรมเชิงฟังก์ชัน แต่ Rubyists หลายคนเริ่มเห็นคุณค่าของการทำงานในรูปแบบการใช้งาน หลักการสำคัญประการหนึ่งของรูปแบบนี้คือ คุณควรหลีกเลี่ยงผลข้างเคียง ออบเจ็กต์ไม่ควรเปลี่ยนแปลงหลังจากเริ่มต้นแล้ว

การเรียกใช้เมธอดการตรึงภายในคอนสตรัคเตอร์ทำให้สามารถรับประกันได้ว่าอ็อบเจกต์จะไม่มีวันเปลี่ยนแปลง ผลข้างเคียงที่ไม่ได้ตั้งใจจะส่งผลให้มีการยกข้อยกเว้น

class Point
  attr_accessor :x, :y
  def initialize(x, y)
    @x = x
    @y = y
    freeze
  end

  def change
    @x = 3
  end
end

point = Point.new(1,2)
point.change # RuntimeError: can't modify frozen Point