आज मैं प्रदर्शन में सुधार के लिए अपनी पसंदीदा तकनीकों में से एक के बारे में बात करना चाहता था। यह आसान छोटे प्रदर्शन जीत का एक स्रोत है जो अंततः जुड़ जाते हैं, और केवल कभी-कभी आपके एप्लिकेशन को धुएं के ढेर में बदल देते हैं। बहुत कभी-कभी।
इस तकनीक को "मेमोइज़ेशन" कहा जाता है। $10 कंप्यूटर-साइंस शब्द के बावजूद, इसका सीधा सा मतलब है कि, हर बार मेथड कॉल करने पर वही काम करने के बजाय, आप रिटर्न वैल्यू को एक वेरिएबल में सेव करते हैं और उसके बजाय उसका उपयोग करते हैं।
यहाँ छद्म कोड में यह कैसा दिखता है:
def my_method
@memo = <work> if @memo is undefined
return @memo
end
और यहाँ आप इसे Ruby में कैसे करते हैं। यह सबसे मजबूत दृष्टिकोण है, लेकिन यह काफी वर्बोज़ है। अन्य, अधिक संक्षिप्त दृष्टिकोण हैं जिन पर हम बाद में चर्चा करेंगे।
class MyClass
def my_method
unless defined?(@my_method)
@my_method = begin
# अपना कैलकुलेशन, डेटाबेस क्वेरी
# या अन्य लंबे समय तक चलने वाला काम यहां करें।
end
end
@my_method
end
end
उपरोक्त कोड तीन चीजें करता है:
- जांचता है कि क्या
@my_methodनाम का एक इंस्टेंस वेरिएबल मौजूद है। - यदि है, तो यह कुछ काम करता है और परिणाम को
@my_methodमें सेव करता है। - यह
@my_methodलौटाता है
इस तथ्य से भ्रमित न हों कि हमारे पास my_method नाम का एक मेथड और एक इंस्टेंस वेरिएबल दोनों हैं। मैं अपने वेरिएबल को कुछ भी नाम दे सकता था, लेकिन मेमोइज़ किए जा रहे मेथड के नाम पर इसका नाम रखना कन्वेंशन है।
एक संक्षिप्त संस्करण
उपरोक्त कोड के साथ एक समस्या यह है कि यह थोड़ा बोझिल है। इस वजह से, आप एक संक्षिप्त संस्करण देखने की अधिक संभावना रखते हैं जो लगभग वही काम करता है:
class MyClass
def my_method1
@my_method1 ||= some_long_calculation
end
def my_method2
@my_method2 ||= begin
# begin-end ब्लॉक आपको आसानी से
# यहां कई लाइनों कोड का उपयोग करने देता है।
end
end
end
ये दोनों Ruby के a ||= b ऑपरेटर का उपयोग करते हैं, जो a || (a = b) के लिए शॉर्टहैंड है, जो स्वयं कमोबेश इसके लिए शॉर्टहैंड है:
# आप वास्तविक जीवन में इस तरह return का उपयोग नहीं करेंगे।
# मैं सिर्फ शुरुआती लोगों को यह विचार व्यक्त करने के लिए इसका उपयोग कर रहा हूँ
# कि कंडीशनल का मूल्यांकन जो भी `a` में समाप्त होता है उस पर होता है।
if a
return a
else
a = b
return a
end
बग्स का एक स्रोत
यदि आप बहुत ध्यान दे रहे हैं तो आपने देखा होगा कि संक्षिप्त संस्करण मेमो वेरिएबल के "ट्रुथीनेस" का मूल्यांकन करते हैं बजाय इसके अस्तित्व की जांच करने के। यह संक्षिप्त संस्करण की प्रमुख सीमाओं में से एक का स्रोत है: यह nil या false को कभी मेमोइज़ नहीं करता।
ऐसे बहुत सारे उपयोग के मामले हैं जहां यह महत्वपूर्ण नहीं है। लेकिन यह उन कष्टप्रद तथ्यों में से एक है जिसे आपको हमेशा अपने दिमाग के पीछे रखना होगा जब भी आप मेमोइज़ कर रहे हों।
आर्गुमेंट्स वाले मेथड्स को मेमोइज़ करना
अब तक हमने केवल सिंगल वैल्यूज़ को मेमोइज़ करने का काम किया है। लेकिन कई फंक्शन्स हर समय एक ही परिणाम नहीं लौटाते। आइए तकनीकी इंटरव्यू के एक पुराने पसंदीदा पर नज़र डालते हैं: फाइबोनैचि अनुक्रम।
आप Ruby में फाइबोनैचि अनुक्रम को पुनरावर्ती रूप से इस तरह कैलकुलेट कर सकते हैं:
class Fibonacci
def self.calculate(n)
return n if n == 0 || n == 1
calculate(n - 1) + calculate(n - 2)
end
end
Fibonacci.calculate(10) # => 55
इस कार्यान्वयन के साथ समस्या यह है कि यह अकुशल है। इसे साबित करने के लिए, आइए n के मान को देखने के लिए एक print स्टेटमेंट जोड़ें।
class Fibonacci
def self.calculate(n)
print "#{ n } "
return n if n == 0 || n == 1
calculate(n - 1) + calculate(n - 2)
end
end
Fibonacci.calculate(4)
# आउटपुट: 4 3 2 1 0 1 2 1 0
जैसा कि आप देख सकते हैं, calculate को n के समान मानों के साथ बार-बार कॉल किया जा रहा है। वास्तव में calculate के कॉल्स की संख्या n के साथ एक्सपोनेंशियली बढ़ने वाली है।
इसका एक तरीका calculate के परिणामों को मेमोइज़ करना है। ऐसा करना हमारे द्वारा कवर किए गए अन्य मेमोइज़ेशन उदाहरणों से बहुत अलग नहीं है।
class Fibonacci
def self.calculate(n)
@calculate ||= {}
@calculate[n] ||= begin
print "#{ n } "
if n == 0 || n == 1
n
else
calculate(n - 1) + calculate(n - 2)
end
end
end
end
Fibonacci.calculate(4)
# आउटपुट: 4 3 2 1 0
अब जब हमने calculate को मेमोइज़ कर लिया है, तो कॉल्स की संख्या अब n के साथ एक्सपोनेंशियली नहीं बढ़ती।
Fibonacci.calculate(20)
# आउटपुट: 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
अमान्यता
मेमोइज़ेशन कैशिंग की तरह है, सिवाय इसके कि मेमोइज़्ड परिणाम स्वचालित रूप से समाप्त नहीं होते हैं, और एक बार सेट होने के बाद उन्हें साफ़ करने का आमतौर पर कोई आसान तरीका नहीं होता।
फाइबोनैचि अनुक्रम जनरेटर जैसे उपयोग-मामलों के लिए यह शायद ही मायने रखता है। Fibonacci.calculate(10) हमेशा वही परिणाम लौटाएगा। लेकिन अन्य उपयोग के मामलों में यह मायने रखता है।
उदाहरण के लिए, आप ऐसा कोड देख सकते हैं:
# सबसे अच्छा विचार नहीं
class User
def full_name
@full_name ||= [first_name, last_name].join(" ")
end
end
व्यक्तिगत रूप से, मैं यहां मेमोइज़ेशन का उपयोग नहीं करूंगा क्योंकि यदि पहला या अंतिम नाम बदलता है, तो पूरा नाम अपडेट नहीं हो सकता है।
एक जगह जहां आप थोड़ा अधिक लापरवाह हो सकते हैं वह है Rails कंट्रोलर्स के अंदर। ऐसा कोड देखना बहुत आम है:
class ApplicationController
def current_user
@current_user ||= User.find(...)
end
end
यह ठीक है, क्योंकि कंट्रोलर इंस्टेंस प्रत्येक वेब रिक्वेस्ट के बाद नष्ट हो जाता है। किसी सामान्य रिक्वेस्ट के दौरान वर्तमान में लॉग-इन यूजर के बदलने की संभावना नहीं है।
एक्शनकेबल जैसे स्ट्रीमिंग कनेक्शन के साथ काम करते समय आपको अधिक सावधान रहने की आवश्यकता हो सकती है। मुझे नहीं पता। मैंने कभी इसका उपयोग नहीं किया।
अति-उपयोग
अंत में मुझे लगता है कि मुझे यह बताना चाहिए कि किसी भी चीज़ की तरह, मेमोइज़ेशन को बहुत दूर तक ले जाना संभव है। यह एक तकनीक है जिसे वास्तव में केवल महंगे ऑपरेशन्स पर लागू किया जाना चाहिए जो मेमो वेरिएबल के जीवनकाल के दौरान कभी नहीं बदलेंगे।