আজ আমি আমার প্রিয় টেকনিকগুলির একটি নিয়ে কথা বলতে চাই পারফরম্যান্স উন্নত করার জন্য। এটি সহজ ছোট পারফরম্যান্স জয়ের একটি উৎস যা পরপর যোগ হয়, এবং কেবল কদাচিত আপনার অ্যাপ্লিকেশনকে ধোঁয়া র entsprের একটি ঢেরে রূপান্তরিত করে। খুব কদাচিত।
এই টেকনিকটি "মেমোইজেশন" নামে পরিচিত। $10 কম্পিউটার সায়েন্স শব্দটির باوجودে, এটি কেবল এই মানে: একই কাজ বারবার করার পরিবর্তে প্রতিবার একটি মেথড কল করলে, আপনি রিটার্ন ভ্যালু একটি ভেরিয়েবলে সংরক্ষণ করেন এবং সেটি ব্যবহার করেন।
সিউডোকোডে এটি কেমন দেখায়:
def my_method
@memo = if @memo is undefined
return @memo
end
এবং রুবিতে এটি কীভাবে করবেন। এটি সবচেয়ে রোবাস্ট পদ্ধতি, কিন্তু এটি খুব বর্বাদ। আরও সংক্ষিপ্ত পদ্ধতিগুলোও আছে जिनके बारेে আমরা পরে আলোচনা করব।
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
এই দুটি রুবির a ||= b অপারেটর ব্যবহার করে, যা a || (a = b) এর শর্টকাট, যা নিজে আরও-অধিক বা কম হলেও if a; return a; else; a = b; return a; end এর শর্টকাট।
# আপনি রিয়েল লাইফে এইভাবে return ব্যবহার করবেন না।
# আমি শুধু বিগিনারদের বোঝানোর জন্য এটি ব্যবহার করছি
# যে কন্ডিশনালটি তো 評価 করে যেটি `a`-তে থাকে।
if a
return a
else
a = b
return a
end
বাগের উৎস
আপনি যদি খুব সংলগ্নভাবে ফোকাস করেন তবে আপনি লক্ষ্য করতে পারেন যে সংক্ষিপ্ত ভার্সনগুলো মেমো ভেরিয়েবলের "truthiness" ইভ্যালুয়েট করে এর এক্সিস্টেন্স চেক করার বদলে। এটিই সংক্ষিপ্ত ভার্সনের একটি বড় লিমিটেশনের উৎস: এটি কখনো nil বা false মেমোইজ করে না।
অনেক ইউজ কেস আছে যেখানে এটি গুরুত্বপূর্ণ নয়। কিন্তু এটি সেই বিরক্তিকর ফ্যাক্টগুলোর একটি যা আপনাকে মেমোইজ করার সময় মস্তিষ্কের পেছনে রাখতে হয়।
আর্গুমেন্ট সহ মেথড মেমোইজ করা
এ পর্যন্ত আমরা শুধু একক মান মেমোইজ করার সাথে কাজ করেছি। কিন্তু সব ফাংশন সব সময় একই ফলাফল রিটার্ন করে না। চলুন টেকনিক্যাল ইন্টারভিউর একটি পুরানো প্রিয় বিষয় নিই: ফিবোনাচি সিকোয়েন্স।
আপনি রুবিতে ফিবোনাচি সিকোয়েন্স রিকারসিভলি ক্যালকুলেট করতে পারেন:
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
ইনভ্যালিডেশন
মেমোইজেশন ক্যাশিং-এর rấtคลম, বাদে মেমোইজড ফলাফলগুলো অটোমেটিক্যালি এক্সপায়ার হয় না, এবং একবার সেট হয়ে গেলে সেগুলো ক্লিয়ার করার সাধারণত কোনো সহজ উপায় নেই।
ফিবোনাচি সিকোয়েন্স জেনারেটরের মত ইউজ-কেসের জন্য এটি কম গুরুত্বপূর্ণ। Fibonacci.calculate(10) সবসময় একই ফলাফল রিটার্ন করবে। কিন্তু অন্যান্য ইউজ কেসে এটি গুরুত্বপূর্ণ।
উদাহরণস্বরূপ, আপনি নিচের কোডের মত কিছু দেখতে পा सकते हैं:
# ভালো আইডিয়া নয়
class User
def full_name
@full_name ||= [first_name, last_name].join(" ")
end
end
ব্যক্তিগতভাবে, আমি এখানে মেমোইজেশন ব্যবহার করব না কারণ যদি ফাস্ট বা লাস্ট নাম পরিবর্তন হয়, তবে ফুল নেম আপডেট নাও হতে পারে।
वह একটি জায়গা যেখানে আপনি কিছুটা নমনীয় হতে পারেন তা হলো রেলস কন্ট্রোলারের ভিতরে। এখানে নিচের কোডের মত কোড দেখা খুব সাধারণ:
class ApplicationController
def current_user
@current_user ||= User.find(...)
end
end
এটি ঠিক আছে, কারণ কন্ট্রোলার ইনস্ট্যান্স প্রতিটি ওয়েব রিকোয়ারেন্টের পর নষ্ট হয়ে যায়। কোনো সাধারণ রিকোয়ারেন্টে বর্তমান লগড-ইন ইউজার পরিবর্তন হওয়া অসম্ভব।
স্ট্রিমিং কনেকশন যেমন ActionCable ডিল করার সময় আপনাকে আরও সতর্ক হতে হতে পারে। আমি জানি না। আমি কখনো ব্যবহার করিনি।
অব্যবহার
শেষে আমি মনে করি যে যেকোনো জিনিসের মত, মেমোইজেশনকে অতিরিক্ত দূর तक নিয়ে যাওয়া সম্ভব। এটি একটি টেকনিক যা কেবল ব্যয়বহুল অপারেশনে প্রয়োগ করা উচিত যা মেমো ভেরিয়েবলের লাইফটাইম ধরে কখনো পরিবর্তন হবে না।