आजकल Ruby कोड में #freeze का उपयोग देखना बहुत आम है। लेकिन यह अक्सर पूरी तरह से स्पष्ट नहीं होता कि freeze का उपयोग क्यों किया जा रहा है। इस पोस्ट में हम उन सबसे सामान्य कारणों को देखेंगे जिनकी वजह से एक डेवलपर वेरिएबल्स को फ्रीज कर सकता है। प्रत्येक कारण को स्पष्ट करने के लिए, मैंने 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") जैसा मेथड कॉल करते हैं, तो आप एक नया String ऑब्जेक्ट बनाते हैं। यदि आपका कोड इस तरह के मेथड को प्रति सेकंड हजारों बार कॉल करता है, तो इसका मतलब है कि आप प्रति सेकंड हजारों स्ट्रिंग्स बना (और गार्बेज-कलेक्ट कर) रहे हैं। यह बहुत अधिक ओवरहेड है!
सौभाग्य से, Ruby हमें एक रास्ता देता है। यदि हम स्ट्रिंग लिटरल्स को फ्रीज करते हैं, तो 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
# 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 राउटर को देखें। चूंकि राउटर का उपयोग हर वेब पेज रिक्वेस्ट के लिए किया जाता है, इसलिए इसे तेज़ होना चाहिए। इसका मतलब है बहुत सारे फ्रोज़न स्ट्रिंग लिटरल्स।
# 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"}
# Ruby >= 2.2 में
user["name"] # ...यह Ruby <= 2.1 में इसके समतुल्य है
user["name".freeze]
और Matz के अनुसार, Ruby 3 में सभी स्ट्रिंग लिटरल्स डिफ़ॉल्ट रूप से स्वचालित रूप से फ्रीज हो जाएंगे।
link: DevelopersMeeting20150820Japan - String literals are frozen (immutable) by default in Ruby 3.0
https://t.co/4XcelftmSa
— Yukihiro Matsumoto (@yukihiro_matz) August 20, 2015
वैल्यू ऑब्जेक्ट्स और फंक्शनल प्रोग्रामिंग
हालांकि Ruby एक फंक्शनल प्रोग्रामिंग भाषा नहीं है, कई Rubyists फंक्शनल स्टाइल में काम करने का मूल्य देखने लगे हैं। इस स्टाइल के मुख्य सिद्धांतों में से एक है कि आपको साइड इफेक्ट्स से बचना चाहिए। इनिशियलाइज़ेशन के बाद ऑब्जेक्ट्स को कभी नहीं बदलना चाहिए।
कंस्ट्रक्टर के अंदर freeze मेथड को कॉल करके, यह गारंटी देना संभव है कि एक ऑब्जेक्ट कभी नहीं बदलेगा। कोई भी अनजाने साइड-इफेक्ट्स एक एक्सेप्शन उठाएंगे।
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