অ্যাপ্লিকেশনগুলি বিকাশ করার সময় আমাদের প্রায়শই এমন পদ্ধতি থাকে যা ধীরে ধীরে চলে। সম্ভবত তাদের ডাটাবেস অনুসন্ধান করতে হবে, বা একটি বাহ্যিক পরিষেবাতে আঘাত করতে হবে, উভয়ই তাদের গতি কমিয়ে দিতে পারে। যখনই আমাদের সেই ডেটার প্রয়োজন হয় তখন আমরা পদ্ধতিটিকে কল করতে পারি এবং শুধুমাত্র ওভারহেড গ্রহণ করতে পারি, কিন্তু যদি কর্মক্ষমতা একটি উদ্বেগ হয় তবে আমাদের কিছু বিকল্প আছে।
একটির জন্য, আমরা একটি ভেরিয়েবলে ডেটা বরাদ্দ করতে পারি এবং এটি পুনরায় ব্যবহার করতে পারি, যা প্রক্রিয়াটিকে দ্রুততর করবে। যদিও একটি সম্ভাব্য সমাধান, ম্যানুয়ালি সেই ভেরিয়েবলটি পরিচালনা করা দ্রুত ক্লান্তিকর হয়ে উঠতে পারে।
কিন্তু, যদি পরিবর্তে, এই "ধীর কাজ" করার পদ্ধতিটি আমাদের জন্য সেই পরিবর্তনশীলকে পরিচালনা করতে পারে? এটি আমাদেরকে একই পদ্ধতিতে কল করার অনুমতি দেবে, তবে পদ্ধতিটি ডেটা সংরক্ষণ এবং পুনরায় ব্যবহার করতে হবে। মেমোাইজেশন ঠিক এটাই করে।
সহজভাবে বললে, মেমোাইজেশন একটি পদ্ধতির রিটার্ন মান সংরক্ষণ করছে তাই একে প্রতিবার পুনরায় গণনা করতে হবে না। সমস্ত ক্যাশিংয়ের মতো, আপনি কার্যকরভাবে সময়ের জন্য মেমরি ট্রেড করছেন (অর্থাৎ আপনি মান সংরক্ষণ করার জন্য প্রয়োজনীয় মেমরি ছেড়ে দেন, তবে আপনি পদ্ধতিটি প্রক্রিয়া করার জন্য প্রয়োজনীয় সময় বাঁচান)।
কীভাবে একটি মান মনে রাখবেন
বা-সমান অপারেটরের সাথে মানগুলি মনে রাখার জন্য রুবি একটি খুব পরিষ্কার বাণী প্রদান করে:||=
. এটি একটি লজিক্যাল OR (||
) ব্যবহার করে ) বাম এবং ডান মানের মধ্যে, তারপর বাম দিকের ভেরিয়েবলে ফলাফল নির্ধারণ করে। কর্মে:
value ||= expensive_method(123)
#logically equivalent to:
value = (value || expensive_method(123))
স্মরণীয়করণ কিভাবে কাজ করে
এটি কীভাবে কাজ করে তা বোঝার জন্য আপনাকে দুটি ধারণা উপলব্ধি করতে হবে:"মিথ্যা" মান এবং অলস মূল্যায়ন। আমরা প্রথমে সত্য-মিথ্যা দিয়ে শুরু করব।
সত্য এবং মিথ্যা
বুলিয়ান true
-এর জন্য রুবি (প্রায় অন্যান্য ভাষার মতো) বিল্ট-ইন কীওয়ার্ড রয়েছে এবং false
মান তারা ঠিক আপনার প্রত্যাশা অনুযায়ী কাজ করে:
if true
#we always run this
end
if false
# this will never run
end
যাইহোক, রুবির (এবং অন্যান্য অনেক ভাষা) "সত্য" এবং "মিথ্যা" মানগুলির ধারণাও রয়েছে। এর অর্থ হল মানগুলিকে "যেমন" হিসাবে বিবেচনা করা যেতে পারে সেগুলি true
অথবা false
. রুবিতে শুধু nil
এবং false
মিথ্যা হয় অন্য সব মান (শূন্য সহ) true
হিসেবে ধরা হয় (দ্রষ্টব্য:অন্যান্য ভাষা বিভিন্ন পছন্দ করে। উদাহরণস্বরূপ C শূন্যকে false
হিসাবে বিবেচনা করে ) উপরে থেকে আমাদের উদাহরণ পুনরায় ব্যবহার করে, আমরা লিখতে পারি:
value = "abc123" # a string
if value
# we always run this
end
value = nil
if value
# this will never run
end
অলস মূল্যায়ন
অলস মূল্যায়ন হল এক ধরনের অপ্টিমাইজেশন যা প্রোগ্রামিং ভাষায় খুবই সাধারণ। এটি প্রোগ্রামটিকে অপ্রয়োজনীয় ক্রিয়াকলাপগুলি এড়ানোর অনুমতি দেয়৷
লজিক্যাল বা অপারেটর (||
) বাম বা ডান দিক সত্য হলে সত্য ফেরত দেয়। এর মানে হল যে যদি বাম হাতের যুক্তিটি সত্য হয় তবে ডানদিকের দিকটি মূল্যায়ন করার কোন মানে নেই কারণ আমরা ইতিমধ্যে জানি ফলাফলটি সত্য হবে৷ যদি আমরা নিজেরাই এটি বাস্তবায়ন করি তবে আমরা এইরকম কিছু দিয়ে শেষ করতে পারি:
def logical_or (lhs, rhs)
return lhs if lhs
rhs
end
যদি lhs
এবং rhs
ফাংশন ছিল (যেমন lamdas) তাহলে আপনি rhs
দেখতে পারেন শুধুমাত্র lhs
হলেই চালানো হবে মিথ্যা।
বা-সমান
সত্য-মিথ্যা মান এবং অলস মূল্যায়নের এই দুটি ধারণার সমন্বয় আমাদের দেখায় যে ||=
অপারেটর করছে:
value #defaults to nil
value ||= "test"
value ||= "blah"
puts value
=> test
আমরা nil
মান দিয়ে শুরু করি কারণ এটি আরম্ভ করা হয়নি। এরপরে, আমরা আমাদের প্রথম ||=
এর মুখোমুখি হই অপারেটর. value
এই পর্যায়ে মিথ্যা তাই আমরা ডানদিকের দিকটি মূল্যায়ন করি ("test"
) এবং ফলাফলটি value
-এ বরাদ্দ করুন এখন আমরা দ্বিতীয় ||=
হিট করি অপারেটর, কিন্তু এই সময় value
"test"
মান থাকায় এটি সত্য . আমরা ডানদিকের মূল্যায়ন এড়িয়ে যাই এবং value
দিয়ে চালিয়ে যাই অস্পৃশ্য।
কখন মেমোাইজেশন ব্যবহার করতে হবে তা নির্ধারণ করা
মেমোাইজেশন ব্যবহার করার সময় কিছু প্রশ্ন আমাদের নিজেদেরকে জিজ্ঞাসা করতে হবে:কত ঘন ঘন মান অ্যাক্সেস করা হয়? এটা পরিবর্তন করার কারণ কি? কত ঘন ঘন এটি পরিবর্তিত হয়?
যদি মানটি শুধুমাত্র একবার অ্যাক্সেস করা হয় তবে মানটি ক্যাশে করা খুব কার্যকর হবে না, যতবার মানটি অ্যাক্সেস করা হবে, আমরা এটি ক্যাশে করার থেকে তত বেশি সুবিধা পেতে পারি।
যখন এটি পরিবর্তনের কারণ কী তা আসে আমাদের পদ্ধতিতে কী মানগুলি ব্যবহার করা হয় তা দেখতে হবে। এটা যুক্তি লাগে? যদি তাই হয়, memoization সম্ভবত এই অ্যাকাউন্টে নিতে হবে. ব্যক্তিগতভাবে, আমি এর জন্য মেমোইস্ট রত্ন ব্যবহার করতে পছন্দ করি কারণ এটি আপনার পক্ষে যুক্তি পরিচালনা করে৷
সবশেষে, আমাদের বিবেচনা করতে হবে কত ঘন ঘন মান পরিবর্তন হয়। সেখানে কি দৃষ্টান্ত ভেরিয়েবল আছে যা এটি পরিবর্তন করতে পারে? তারা পরিবর্তন যখন আমরা ক্যাশে মান সাফ করতে হবে? মানটি কি অবজেক্ট লেভেলে ক্যাশে করা উচিত নাকি ক্লাস লেভেলে?
এই প্রশ্নের উত্তর দেওয়ার জন্য আসুন একটি সহজ উদাহরণ দেখি এবং সিদ্ধান্তগুলির মাধ্যমে ধাপে ধাপে দেখি:
class ProfitLossReport
def initialize(title, expenses, invoices)
@expenses = expenses
@invoices = invoices
@title = title
end
def title
"#{@title} #{Time.current}"
end
def cost
@expenses.sum(:amount)
end
def revenue
@invoices.sum(:amount)
end
def profit
revenue - cost
end
def average_profit(months)
profit / months.to_f
end
end
কলিং কোড এখানে দেখানো হয়নি, তবে এটি একটি ভাল অনুমান যে title
পদ্ধতিটি সম্ভবত শুধুমাত্র একবার বলা হয়, এটি Time.current
ও ব্যবহার করে তাই এটি মনে রাখার অর্থ হতে পারে মানটি তাৎক্ষণিকভাবে বাসি হয়ে যাবে।
revenue
এবং cost
পদ্ধতিগুলি এমনকি এই শ্রেণীর মধ্যে বেশ কয়েকবার আঘাত করা হয়। প্রদত্ত যে তাদের উভয়েরই ডাটাবেস আঘাত করা প্রয়োজন, কর্মক্ষমতা একটি সমস্যা হয়ে উঠলে তারা মুখস্থ করার জন্য প্রধান প্রার্থী হবে। ধরে নিচ্ছি আমরা এইগুলি মুখস্থ করি, তারপর profit
মনে রাখার দরকার নেই, অন্যথায়, আমরা ন্যূনতম লাভের জন্য ক্যাশিংয়ের উপরে ক্যাশিং যোগ করছি।
অবশেষে, আমাদের average_profit
আছে . এখানে মান যুক্তির উপর নির্ভর করে তাই আমাদের স্মৃতিচারণকে এটি বিবেচনায় নিতে হবে। একটি সাধারণ ক্ষেত্রে যেমন revenue
আমরা শুধু এটা করতে পারি:
def revenue
@revenue ||= @invoices.sum(:amount)
end
average_profit
জন্য যদিও, পাস করা প্রতিটি আর্গুমেন্টের জন্য আমাদের আলাদা মান দরকার। আমরা এর জন্য মেমোইস্ট ব্যবহার করতে পারি, কিন্তু স্বচ্ছতার জন্য আমরা এখানে আমাদের নিজস্ব সমাধান রোল করব:
def average_profit(months)
@average_profit ||= {}
@average_profit[months] ||= profit / months.to_f
end
এখানে আমরা আমাদের গণনা করা মান ট্র্যাক রাখতে একটি হ্যাশ ব্যবহার করছি। প্রথমে আমরা @average_profit
নিশ্চিত করি আরম্ভ করা হয়েছে, তারপর আমরা হ্যাশ কী হিসাবে পাস করা আর্গুমেন্ট ব্যবহার করি।
ক্লাস লেভেল বা ইনস্ট্যান্স লেভেলে স্মৃতিচারণ
বেশিরভাগ সময় মেমোাইজেশন ইনস্ট্যান্স লেভেলে করা হয়, যার মানে আমরা কম্পিউটেড মান ধরে রাখতে একটি ইনস্ট্যান্স ভেরিয়েবল ব্যবহার করি। এর মানে হল যে যখনই আমরা বস্তুর একটি নতুন উদাহরণ তৈরি করি তখন এটি "ক্যাশেড" মান থেকে উপকৃত হয় না। এখানে একটি খুব সহজ দৃষ্টান্ত:
class MemoizedDemo
def value
@value ||= computed_value
end
def computed_value
puts "Crunching Numbers"
rand(100)
end
end
এই বস্তুটি ব্যবহার করে আমরা ফলাফল দেখতে পারি:
demo = MemoizedDemo.new
=> #<MemoizedDemo:0x00007f95e5d9d398>
demo.value
Crunching Numbers
=> 19
demo.value
=> 19
MemoizedDemo.new.value
Crunching Numbers
=> 93
আমরা শুধুমাত্র একটি ক্লাস-লেভেল ভেরিয়েবল (@@
সহ) ব্যবহার করে এটি পরিবর্তন করতে পারি ) আমাদের মেমোাইজড মানের জন্য:
def value
@@value ||= computed_value
end
ফলাফল তখন হয়ে যায়:
demo = MemoizedDemo.new
=> #<MemoizedDemo:0x00007f95e5d9d398>
demo.value
Crunching Numbers
=> 60
demo.value
=> 60
MemoizedDemo.new.value
=> 60
আপনি প্রায়ই ক্লাস-লেভেল মেমোাইজেশন নাও চাইতে পারেন, কিন্তু এটি একটি বিকল্প হিসাবে আছে। যাইহোক, আপনার যদি এই স্তরে ক্যাশে করার জন্য একটি মান প্রয়োজন হয় তবে এটি সম্ভবত রেডিস বা মেমক্যাশেডের মতো একটি বহিরাগত স্টোরের সাথে মানটি ক্যাশে করার দিকে নজর দেওয়া উচিত৷
রুবি অন রেল অ্যাপ্লিকেশনগুলিতে সাধারণ মেমোাইজেশন ব্যবহারের ক্ষেত্রে
রেল অ্যাপ্লিকেশনগুলিতে, মেমোাইজেশনের জন্য আমি সবচেয়ে সাধারণ ব্যবহার-কেসটি দেখছি ডাটাবেস কলগুলি হ্রাস করা, বিশেষ করে যখন একটি একক অনুরোধের মধ্যে একটি মান পরিবর্তন করা যাচ্ছে না। কন্ট্রোলারে রেকর্ড খোঁজার জন্য "ফাইন্ডার" পদ্ধতিগুলি এই ধরনের ডাটাবেস কলের একটি ভাল উদাহরণ যেমন:
def current_user
@current_user ||= User.find(params[:user_id])
end
আরেকটি সাধারণ জায়গা হল যদি আপনি কিছু ধরনের ডেকোরেটর/উপস্থাপক/ভিউ-মডেল ধরনের আর্কিটেকচার ব্যবহার করেন ভিউ রেন্ডার করার জন্য। এই বস্তুগুলির পদ্ধতিগুলি প্রায়ই মেমোাইজেশনের জন্য ভাল প্রার্থী থাকে কারণ সেগুলি শুধুমাত্র অনুরোধের জীবনের জন্য টিকে থাকে, ডেটা সাধারণত রূপান্তরিত হয় না এবং কিছু পদ্ধতি সম্ভবত ভিউ রেন্ডার করার সময় একাধিকবার আঘাত করা হয়৷
Memoization Gotchas
সবথেকে বড় একটা জিনিস হল মুখস্থ করা যখন এটা সত্যিই প্রয়োজন হয় না। স্ট্রিং ইন্টারপোলেশনের মতো জিনিসগুলি মেমোাইজেশনের জন্য সহজ প্রার্থীর মতো দেখতে পারে, কিন্তু বাস্তবে, সেগুলি আপনার সাইটের পারফরম্যান্সের উপর কোনও লক্ষণীয় প্রভাব ফেলতে পারে না (যদি না আপনি ব্যতিক্রমীভাবে বড় স্ট্রিং ব্যবহার করছেন বা খুব বেশি পরিমাণে স্ট্রিং ম্যানিপুলেশন করছেন), উদাহরণস্বরূপ:
এখানে def title
# memoization here is not going to have much of an impact on our performance
@title ||= "#{@object.published_at} - #{@object.title}"
end
আমাদের পুরানো বন্ধুর ক্যাশে অকার্যকরকরণের জন্য দেখার জন্য আরেকটি জিনিস, বিশেষ করে যদি আপনার মেমো করা মান অবজেক্টের অবস্থার উপর নির্ভর করে। এটি প্রতিরোধে সাহায্য করার একটি উপায় হল আপনি করতে পারেন সর্বনিম্ন স্তরে ক্যাশে করা। a + b
কম্পিউটিং একটি পদ্ধতি ক্যাশ করার পরিবর্তে a
ক্যাশে করা ভাল হতে পারে এবং b
পৃথকভাবে পদ্ধতি।
# Instead of this
def profit
# anyone else calling 'revenue' or 'losses' is not benefitting from the caching here
# and what happens if the 'revenue' or 'losses' value changes, will we remember to update profit?
@profit ||= (revenue - losses)
end
# try this
def profit
# no longer cached, but subtraction is a fast calculation
revenue - losses
end
def revenue
@revenue ||= Invoice.all.sum(:amount)
end
def losses
@losses ||= Purchase.all.sum(:amount)
end
অলস মূল্যায়ন কীভাবে কাজ করে তার কারণে শেষ গোটা হয়েছে - যদি আপনি একটি মিথ্যা মান (অর্থাৎ শূন্য বা মিথ্যা), ||=
মনে রাখতে চান তবে আপনাকে আরও কিছুটা কাস্টম করতে হবে আপনার সংরক্ষিত মান মিথ্যা হলে idiom সর্বদা ডানদিকে কার্যকর করবে। আমার অভিজ্ঞতায়, প্রায়শই আপনাকে এই মানগুলি ক্যাশে করার প্রয়োজন হয় না, তবে আপনি যদি তা করেন তবে এটি ইতিমধ্যে গণনা করা হয়েছে তা বোঝাতে আপনাকে একটি বুলিয়ান পতাকা যোগ করতে হতে পারে বা অন্য ক্যাশিং প্রক্রিয়া ব্যবহার করতে হতে পারে৷
def last_post
# if the user has no posts, we will hit the database every time this method is called
@last_post ||= Post.where(user: current_user).order_by(created_at: :desc).first
end
# As a simple workaround we could do something like:
def last_post
return @last_post if @last_post_checked
@last_post_checked = true
@last_post ||= Post.where(user: current_user).order_by(created_at: :desc).first
end
যখন মেমোাইজেশন যথেষ্ট নয়
আপনার অ্যাপ্লিকেশনের অংশগুলিতে কর্মক্ষমতা উন্নত করার জন্য মেমোাইজেশন একটি সস্তা এবং কার্যকর উপায় হতে পারে, তবে এটি এর ত্রুটিগুলি ছাড়া নয়। একটি বড় এক অধ্যবসায়; সাধারণ ইনস্ট্যান্স-লেভেল মেমোাইজেশনের জন্য, মানটি শুধুমাত্র সেই একটি নির্দিষ্ট বস্তুর জন্য সংরক্ষিত হয়। এটি একটি ওয়েব অনুরোধের জীবনের জন্য মান সংরক্ষণের জন্য মেমোাইজেশনকে দুর্দান্ত করে তোলে, তবে আপনার যদি একাধিক অনুরোধের জন্য একই মান থাকে এবং প্রতিবার পুনরায় গণনা করা হয় তবে এটি আপনাকে ক্যাশিংয়ের সম্পূর্ণ সুবিধা দেয় না।>
ক্লাস-লেভেল মেমোাইজেশন এতে সাহায্য করতে পারে, কিন্তু ক্যাশে অবৈধতা পরিচালনা করা আরও কঠিন হয়ে ওঠে। উল্লেখ করার মতো নয় যে আপনার সার্ভার রিবুট করলে সেই ক্যাশে করা মানগুলি হারিয়ে যাবে এবং সেগুলি একাধিক ওয়েব সার্ভারের মধ্যে ভাগ করা যাবে না৷
ক্যাশিং সম্পর্কিত এই সিরিজের পরবর্তী সংখ্যায় আমরা এই সমস্যাগুলির জন্য রেলের সমাধান দেখব - নিম্ন-স্তরের ক্যাশিং। আপনাকে একটি বহিরাগত স্টোরে মান ক্যাশে করার অনুমতি দেয় যা সার্ভারের মধ্যে ভাগ করা যায় এবং মেয়াদ শেষ হয়ে যাওয়া এবং গতিশীল ক্যাশে কীগুলির সাথে ক্যাশে অবৈধতা পরিচালনা করে৷