ทุกวันนี้เป็นเรื่องปกติที่จะเห็น #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
link:DevelopersMeeting20150820Japan - ตัวอักษรสตริงถูกตรึง (เปลี่ยนไม่ได้) โดยค่าเริ่มต้นใน Ruby 3.0 https://t.co/4XcelftmSa
— Yukihiro Matsumoto (@yukihiro_matz) วันที่ 20 สิงหาคม 2015
ค่าอ็อบเจ็กต์ &ฟังก์ชันการเขียนโปรแกรม
แม้ว่า 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