ജാവാസ്ക്രിപ്റ്റ് മെമ്മോയിസേഷൻ ടെക്നിക്കുകൾ, കാഷിംഗ് തന്ത്രങ്ങൾ, പ്രായോഗിക ഉദാഹരണങ്ങൾ എന്നിവ ഉപയോഗിച്ച് കോഡിന്റെ പ്രകടനം മെച്ചപ്പെടുത്താം. വേഗതയേറിയ പ്രവർത്തനത്തിനായി മെമ്മോയിസേഷൻ പാറ്റേണുകൾ നടപ്പിലാക്കാൻ പഠിക്കുക.
ജാവാസ്ക്രിപ്റ്റ് മെമ്മോയിസേഷൻ പാറ്റേണുകൾ: കാഷിംഗ് തന്ത്രങ്ങളും പ്രകടന മികവും
സോഫ്റ്റ്വെയർ ഡെവലപ്മെന്റ് രംഗത്ത്, പ്രകടനം വളരെ പ്രധാനമാണ്. ഫ്രണ്ട്-എൻഡ് വെബ് ഡെവലപ്മെന്റ് മുതൽ Node.js ഉപയോഗിച്ചുള്ള സെർവർ-സൈഡ് ആപ്ലിക്കേഷനുകൾ വരെ വൈവിധ്യമാർന്ന സാഹചര്യങ്ങളിൽ ഉപയോഗിക്കുന്ന ഒരു ഭാഷയാണ് ജാവാസ്ക്രിപ്റ്റ്. ഇതിന്റെ സുഗമവും കാര്യക്ഷമവുമായ പ്രവർത്തനം ഉറപ്പാക്കാൻ പലപ്പോഴും ഒപ്റ്റിമൈസേഷൻ ആവശ്യമായി വരുന്നു. ചില പ്രത്യേക സാഹചര്യങ്ങളിൽ പ്രകടനം ഗണ്യമായി മെച്ചപ്പെടുത്താൻ കഴിയുന്ന ഒരു ശക്തമായ സാങ്കേതികതയാണ് മെമ്മോയിസേഷൻ.
വളരെയധികം സമയം എടുക്കുന്ന ഫംഗ്ഷൻ കോളുകളുടെ ഫലങ്ങൾ സംഭരിക്കുകയും, അതേ ഇൻപുട്ടുകൾ വീണ്ടും വരുമ്പോൾ കാഷെ ചെയ്ത ഫലം തിരികെ നൽകുകയും ചെയ്യുന്നതിലൂടെ കമ്പ്യൂട്ടർ പ്രോഗ്രാമുകളുടെ വേഗത വർദ്ധിപ്പിക്കാൻ ഉപയോഗിക്കുന്ന ഒരു ഒപ്റ്റിമൈസേഷൻ സാങ്കേതികതയാണ് മെമ്മോയിസേഷൻ. പ്രധാനമായും, ഇത് ഫംഗ്ഷനുകളെ ലക്ഷ്യം വച്ചുള്ള ഒരുതരം കാഷിംഗ് ആണ്. ഈ സമീപനം താഴെ പറയുന്ന തരം ഫംഗ്ഷനുകൾക്ക് വളരെ ഫലപ്രദമാണ്:
- പ്യുവർ (Pure): പാർശ്വഫലങ്ങൾ ഇല്ലാതെ, ഇൻപുട്ട് മൂല്യങ്ങളെ മാത്രം ആശ്രയിച്ച് റിട്ടേൺ മൂല്യം നിർണ്ണയിക്കുന്ന ഫംഗ്ഷനുകൾ.
- ഡിറ്റർമിനിസ്റ്റിക് (Deterministic): ഒരേ ഇൻപുട്ടിന്, ഫംഗ്ഷൻ എല്ലായ്പ്പോഴും ഒരേ ഔട്ട്പുട്ട് നൽകുന്നു.
- എക്സ്പെൻസീവ് (Expensive): കമ്പ്യൂട്ടേഷണലായി വളരെ അധികം സമയം എടുക്കുന്നതോ അല്ലെങ്കിൽ സങ്കീർണ്ണമായ കണക്കുകൂട്ടലുകൾ ഉള്ളതോ ആയ ഫംഗ്ഷനുകൾ (ഉദാഹരണത്തിന്, റിക്കേഴ്സീവ് ഫംഗ്ഷനുകൾ, സങ്കീർണ്ണമായ കണക്കുകൂട്ടലുകൾ).
ഈ ലേഖനം ജാവാസ്ക്രിപ്റ്റിലെ മെമ്മോയിസേഷൻ എന്ന ആശയത്തെക്കുറിച്ചും, അതിലെ വിവിധ പാറ്റേണുകൾ, കാഷിംഗ് തന്ത്രങ്ങൾ, ഇത് നടപ്പിലാക്കുന്നതിലൂടെ നേടാനാകുന്ന പ്രകടന മികവ് എന്നിവയെക്കുറിച്ചും വിശദീകരിക്കുന്നു. വിവിധ സാഹചര്യങ്ങളിൽ മെമ്മോയിസേഷൻ എങ്ങനെ ഫലപ്രദമായി പ്രയോഗിക്കാമെന്ന് വ്യക്തമാക്കുന്നതിന് പ്രായോഗിക ഉദാഹരണങ്ങൾ നമ്മൾ പരിശോധിക്കും.
മെമ്മോയിസേഷൻ മനസ്സിലാക്കാം: അടിസ്ഥാന ആശയം
അടിസ്ഥാനപരമായി, മെമ്മോയിസേഷൻ കാഷിംഗിന്റെ തത്വം പ്രയോജനപ്പെടുത്തുന്നു. ഒരു മെമ്മോയിസ്ഡ് ഫംഗ്ഷനെ ഒരു കൂട്ടം ആർഗ്യുമെന്റുകൾ ഉപയോഗിച്ച് വിളിക്കുമ്പോൾ, ആ ആർഗ്യുമെന്റുകൾക്കുള്ള ഫലം ഇതിനകം കണക്കാക്കി ഒരു കാഷെയിൽ (സാധാരണയായി ഒരു ജാവാസ്ക്രിപ്റ്റ് ഒബ്ജക്റ്റ് അല്ലെങ്കിൽ മാപ്പ്) സംഭരിച്ചിട്ടുണ്ടോ എന്ന് ആദ്യം പരിശോധിക്കുന്നു. ഫലം കാഷെയിൽ കണ്ടെത്തിയാൽ, അത് ഉടനടി തിരികെ നൽകും. ഇല്ലെങ്കിൽ, ഫംഗ്ഷൻ കണക്കുകൂട്ടൽ നടത്തുകയും ഫലം കാഷെയിൽ സംഭരിക്കുകയും അതിനുശേഷം തിരികെ നൽകുകയും ചെയ്യുന്നു.
അനാവശ്യമായ കണക്കുകൂട്ടലുകൾ ഒഴിവാക്കുന്നതിലാണ് ഇതിന്റെ പ്രധാന പ്രയോജനം. ഒരേ ഇൻപുട്ടുകൾ ഉപയോഗിച്ച് ഒരു ഫംഗ്ഷൻ ഒന്നിലധികം തവണ വിളിക്കുകയാണെങ്കിൽ, മെമ്മോയിസ്ഡ് പതിപ്പ് ഒരു തവണ മാത്രമേ കണക്കുകൂട്ടൽ നടത്തുകയുള്ളൂ. തുടർന്നുള്ള കോളുകൾ കാഷെയിൽ നിന്ന് നേരിട്ട് ഫലം വീണ്ടെടുക്കുന്നു, ഇത് കമ്പ്യൂട്ടേഷണലായി സമയം എടുക്കുന്ന പ്രവർത്തനങ്ങൾക്ക് കാര്യമായ പ്രകടന മെച്ചപ്പെടുത്തലുകൾ നൽകുന്നു.
ജാവാസ്ക്രിപ്റ്റിലെ മെമ്മോയിസേഷൻ പാറ്റേണുകൾ
ജാവാസ്ക്രിപ്റ്റിൽ മെമ്മോയിസേഷൻ നടപ്പിലാക്കാൻ നിരവധി പാറ്റേണുകൾ ഉപയോഗിക്കാം. അവയിൽ ഏറ്റവും സാധാരണവും ഫലപ്രദവുമായ ചിലത് നമുക്ക് പരിശോധിക്കാം:
1. ക്ലോഷർ ഉപയോഗിച്ചുള്ള അടിസ്ഥാന മെമ്മോയിസേഷൻ
മെമ്മോയിസേഷന്റെ ഏറ്റവും അടിസ്ഥാനപരമായ സമീപനമാണിത്. ഫംഗ്ഷന്റെ സ്കോപ്പിനുള്ളിൽ ഒരു കാഷെ നിലനിർത്താൻ ഇത് ക്ലോഷർ ഉപയോഗിക്കുന്നു. സാധാരണയായി കാഷെ ഒരു ലളിതമായ ജാവാസ്ക്രിപ്റ്റ് ഒബ്ജക്റ്റായിരിക്കും, അതിൽ കീകൾ (keys) ഫംഗ്ഷൻ ആർഗ്യുമെന്റുകളെയും മൂല്യങ്ങൾ (values) അതിൻ്റെ ഫലങ്ങളെയും പ്രതിനിധീകരിക്കുന്നു.
function memoize(func) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args); // Create a unique key for the arguments
if (cache[key]) {
return cache[key]; // Return cached result
} else {
const result = func.apply(this, args); // Calculate the result
cache[key] = result; // Store the result in the cache
return result; // Return the result
}
};
}
// Example: Memoizing a factorial function
function factorial(n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
const memoizedFactorial = memoize(factorial);
console.time('First call');
console.log(memoizedFactorial(5)); // Calculates and caches
console.timeEnd('First call');
console.time('Second call');
console.log(memoizedFactorial(5)); // Retrieves from cache
console.timeEnd('Second call');
വിശദീകരണം:
- `memoize` എന്ന ഫംഗ്ഷൻ `func` എന്ന ഒരു ഫംഗ്ഷൻ ഇൻപുട്ടായി സ്വീകരിക്കുന്നു.
- അത് അതിൻ്റെ സ്കോപ്പിനുള്ളിൽ (ക്ലോഷർ ഉപയോഗിച്ച്) ഒരു `cache` ഒബ്ജക്റ്റ് ഉണ്ടാക്കുന്നു.
- അത് യഥാർത്ഥ ഫംഗ്ഷനെ പൊതിയുന്ന ഒരു പുതിയ ഫംഗ്ഷൻ തിരികെ നൽകുന്നു.
- ഈ റാപ്പർ ഫംഗ്ഷൻ `JSON.stringify(args)` ഉപയോഗിച്ച് ഫംഗ്ഷൻ ആർഗ്യുമെന്റുകളെ അടിസ്ഥാനമാക്കി ഒരു യുണീക്ക് കീ ഉണ്ടാക്കുന്നു.
- ആ `key` കാഷെയിൽ ഉണ്ടോ എന്ന് പരിശോധിക്കുന്നു. ഉണ്ടെങ്കിൽ, കാഷെ ചെയ്ത മൂല്യം തിരികെ നൽകുന്നു.
- ആ `key` ഇല്ലെങ്കിൽ, യഥാർത്ഥ ഫംഗ്ഷനെ വിളിച്ച്, ഫലം `cache`-ൽ സംഭരിച്ച്, ആ ഫലം തിരികെ നൽകുന്നു.
പരിമിതികൾ:
- സങ്കീർണ്ണമായ ഒബ്ജക്റ്റുകൾക്ക് `JSON.stringify` വേഗത കുറഞ്ഞതാകാം.
- വ്യത്യസ്ത ക്രമത്തിൽ ആർഗ്യുമെന്റുകൾ സ്വീകരിക്കുന്ന ഫംഗ്ഷനുകൾക്കോ, ഒരേ കീകളുള്ളതും എന്നാൽ വ്യത്യസ്ത ക്രമീകരണമുള്ളതുമായ ഒബ്ജക്റ്റുകൾക്കോ കീ ഉണ്ടാക്കുന്നത് പ്രശ്നകരമാവാം.
- `JSON.stringify(NaN)` എന്നത് `null` നൽകുന്നതിനാൽ `NaN` ശരിയായി കൈകാര്യം ചെയ്യുന്നില്ല.
2. കസ്റ്റം കീ ജനറേറ്റർ ഉപയോഗിച്ചുള്ള മെമ്മോയിസേഷൻ
`JSON.stringify` യുടെ പരിമിതികൾ മറികടക്കാൻ, ഫംഗ്ഷന്റെ ആർഗ്യുമെന്റുകളെ അടിസ്ഥാനമാക്കി ഒരു യുണീക്ക് കീ നിർമ്മിക്കുന്ന ഒരു കസ്റ്റം കീ ജനറേറ്റർ ഫംഗ്ഷൻ നിങ്ങൾക്ക് ഉണ്ടാക്കാം. ഇത് കാഷെ എങ്ങനെ ഇൻഡെക്സ് ചെയ്യപ്പെടുന്നു എന്നതിൽ കൂടുതൽ നിയന്ത്രണം നൽകുകയും ചില സാഹചര്യങ്ങളിൽ പ്രകടനം മെച്ചപ്പെടുത്തുകയും ചെയ്യും.
function memoizeWithKey(func, keyGenerator) {
const cache = {};
return function(...args) {
const key = keyGenerator(...args);
if (cache[key]) {
return cache[key];
} else {
const result = func.apply(this, args);
cache[key] = result;
return result;
}
};
}
// Example: Memoizing a function that adds two numbers
function add(a, b) {
console.log('Calculating...');
return a + b;
}
// Custom key generator for the add function
function addKeyGenerator(a, b) {
return `${a}-${b}`;
}
const memoizedAdd = memoizeWithKey(add, addKeyGenerator);
console.log(memoizedAdd(2, 3)); // Calculates and caches
console.log(memoizedAdd(2, 3)); // Retrieves from cache
console.log(memoizedAdd(3, 2)); // Calculates and caches (different key)
വിശദീകരണം:
- ഈ പാറ്റേൺ അടിസ്ഥാന മെമ്മോയിസേഷന് സമാനമാണ്, പക്ഷേ ഇത് ഒരു അധിക ആർഗ്യുമെന്റ് സ്വീകരിക്കുന്നു: `keyGenerator`.
- `keyGenerator` എന്നത് യഥാർത്ഥ ഫംഗ്ഷന്റെ അതേ ആർഗ്യുമെന്റുകൾ എടുത്ത് ഒരു യുണീക്ക് കീ തിരികെ നൽകുന്ന ഒരു ഫംഗ്ഷനാണ്.
- സങ്കീർണ്ണമായ ഡാറ്റാ ഘടനകളുമായി പ്രവർത്തിക്കുന്ന ഫംഗ്ഷനുകൾക്ക് ഇത് കൂടുതൽ വഴക്കമുള്ളതും കാര്യക്ഷമവുമായ കീ നിർമ്മാണം സാധ്യമാക്കുന്നു.
3. മാപ്പ് (Map) ഉപയോഗിച്ചുള്ള മെമ്മോയിസേഷൻ
ജാവാസ്ക്രിപ്റ്റിലെ `Map` ഒബ്ജക്റ്റ് കാഷെ ചെയ്ത ഫലങ്ങൾ സംഭരിക്കാൻ കൂടുതൽ ശക്തവും വൈവിധ്യപൂർണ്ണവുമായ ഒരു മാർഗ്ഗം നൽകുന്നു. സാധാരണ ജാവാസ്ക്രിപ്റ്റ് ഒബ്ജക്റ്റുകളിൽ നിന്ന് വ്യത്യസ്തമായി, ഒബ്ജക്റ്റുകളും ഫംഗ്ഷനുകളും ഉൾപ്പെടെ ഏത് ഡാറ്റാ ടൈപ്പും കീകളായി ഉപയോഗിക്കാൻ `Map` അനുവദിക്കുന്നു. ഇത് ആർഗ്യുമെന്റുകൾ സ്ട്രിംഗിഫൈ ചെയ്യേണ്ടതിന്റെ ആവശ്യകത ഇല്ലാതാക്കുകയും കീ ഉണ്ടാക്കുന്നത് ലളിതമാക്കുകയും ചെയ്യുന്നു.
function memoizeWithMap(func) {
const cache = new Map();
return function(...args) {
const key = args.join('|'); // Create a simple key (can be more sophisticated)
if (cache.has(key)) {
return cache.get(key);
} else {
const result = func.apply(this, args);
cache.set(key, result);
return result;
}
};
}
// Example: Memoizing a function that concatenates strings
function concatenate(str1, str2) {
console.log('Concatenating...');
return str1 + str2;
}
const memoizedConcatenate = memoizeWithMap(concatenate);
console.log(memoizedConcatenate('hello', 'world')); // Calculates and caches
console.log(memoizedConcatenate('hello', 'world')); // Retrieves from cache
വിശദീകരണം:
- ഈ പാറ്റേൺ കാഷെ സംഭരിക്കാൻ ഒരു `Map` ഒബ്ജക്റ്റ് ഉപയോഗിക്കുന്നു.
- സാധാരണ ജാവാസ്ക്രിപ്റ്റ് ഒബ്ജക്റ്റുകളുമായി താരതമ്യപ്പെടുത്തുമ്പോൾ `Map` ഒബ്ജക്റ്റുകളും ഫംഗ്ഷനുകളും ഉൾപ്പെടെ ഏത് ഡാറ്റാ ടൈപ്പും കീകളായി ഉപയോഗിക്കാൻ നിങ്ങളെ അനുവദിക്കുന്നു, ഇത് കൂടുതൽ വഴക്കം നൽകുന്നു.
- `Map` ഒബ്ജക്റ്റിന്റെ `has`, `get` മെത്തേഡുകൾ യഥാക്രമം കാഷെ ചെയ്ത മൂല്യങ്ങൾ പരിശോധിക്കുന്നതിനും വീണ്ടെടുക്കുന്നതിനും ഉപയോഗിക്കുന്നു.
4. റിക്കേഴ്സീവ് മെമ്മോയിസേഷൻ
റിക്കേഴ്സീവ് ഫംഗ്ഷനുകൾ ഒപ്റ്റിമൈസ് ചെയ്യുന്നതിന് മെമ്മോയിസേഷൻ വളരെ ഫലപ്രദമാണ്. ഇടയ്ക്കുള്ള കണക്കുകൂട്ടലുകളുടെ ഫലങ്ങൾ കാഷെ ചെയ്യുന്നതിലൂടെ, നിങ്ങൾക്ക് അനാവശ്യമായ കമ്പ്യൂട്ടേഷനുകൾ ഒഴിവാക്കാനും എക്സിക്യൂഷൻ സമയം ഗണ്യമായി കുറയ്ക്കാനും കഴിയും.
function memoizeRecursive(func) {
const cache = {};
function memoized(...args) {
const key = String(args);
if (cache[key]) {
return cache[key];
} else {
cache[key] = func(memoized, ...args);
return cache[key];
}
}
return memoized;
}
// Example: Memoizing a Fibonacci sequence function
function fibonacci(memoized, n) {
if (n <= 1) {
return n;
}
return memoized(n - 1) + memoized(n - 2);
}
const memoizedFibonacci = memoizeRecursive(fibonacci);
console.time('First call');
console.log(memoizedFibonacci(10)); // Calculates and caches
console.timeEnd('First call');
console.time('Second call');
console.log(memoizedFibonacci(10)); // Retrieves from cache
console.timeEnd('Second call');
വിശദീകരണം:
- `memoizeRecursive` ഫംഗ്ഷൻ ഇൻപുട്ടായി `func` എന്ന ഫംഗ്ഷൻ എടുക്കുന്നു.
- അത് അതിന്റെ സ്കോപ്പിനുള്ളിൽ ഒരു `cache` ഒബ്ജക്റ്റ് ഉണ്ടാക്കുന്നു.
- അത് യഥാർത്ഥ ഫംഗ്ഷനെ പൊതിയുന്ന `memoized` എന്ന പുതിയ ഫംഗ്ഷൻ തിരികെ നൽകുന്നു.
- `memoized` ഫംഗ്ഷൻ നൽകിയിരിക്കുന്ന ആർഗ്യുമെന്റുകൾക്കുള്ള ഫലം ഇതിനകം കാഷെയിൽ ഉണ്ടോയെന്ന് പരിശോധിക്കുന്നു. ഉണ്ടെങ്കിൽ, അത് കാഷെ ചെയ്ത മൂല്യം തിരികെ നൽകുന്നു.
- ഫലം കാഷെയിൽ ഇല്ലെങ്കിൽ, അത് യഥാർത്ഥ ഫംഗ്ഷനെ `memoized` ഫംഗ്ഷൻ തന്നെ ആദ്യത്തെ ആർഗ്യുമെന്റായി നൽകി വിളിക്കുന്നു. ഇത് യഥാർത്ഥ ഫംഗ്ഷനെ അതിന്റെ മെമ്മോയിസ്ഡ് പതിപ്പിനെ റിക്കേഴ്സീവായി വിളിക്കാൻ അനുവദിക്കുന്നു.
- ഫലം പിന്നീട് കാഷെയിൽ സംഭരിക്കുകയും തിരികെ നൽകുകയും ചെയ്യുന്നു.
5. ക്ലാസ്-ബേസ്ഡ് മെമ്മോയിസേഷൻ
ഓബ്ജക്റ്റ്-ഓറിയന്റഡ് പ്രോഗ്രാമിംഗിൽ, മെത്തേഡുകളുടെ ഫലങ്ങൾ കാഷെ ചെയ്യുന്നതിനായി ഒരു ക്ലാസ്സിനുള്ളിൽ മെമ്മോയിസേഷൻ നടപ്പിലാക്കാൻ കഴിയും. ഒരേ ആർഗ്യുമെന്റുകളോടുകൂടി ഇടയ്ക്കിടെ വിളിക്കപ്പെടുന്ന, കമ്പ്യൂട്ടേഷണലായി സമയം എടുക്കുന്ന മെത്തേഡുകൾക്ക് ഇത് ഉപയോഗപ്രദമാകും.
class MemoizedClass {
constructor() {
this.cache = {};
}
memoizeMethod(func) {
return (...args) => {
const key = JSON.stringify(args);
if (this.cache[key]) {
return this.cache[key];
} else {
const result = func.apply(this, args);
this.cache[key] = result;
return result;
}
};
}
// Example: Memoizing a method that calculates the power of a number
power(base, exponent) {
console.log('Calculating power...');
return Math.pow(base, exponent);
}
}
const memoizedInstance = new MemoizedClass();
const memoizedPower = memoizedInstance.memoizeMethod(memoizedInstance.power);
console.log(memoizedPower(2, 3)); // Calculates and caches
console.log(memoizedPower(2, 3)); // Retrieves from cache
വിശദീകരണം:
- `MemoizedClass` അതിന്റെ കൺസ്ട്രക്ടറിൽ ഒരു `cache` പ്രോപ്പർട്ടി നിർവചിക്കുന്നു.
- `memoizeMethod` ഒരു ഫംഗ്ഷൻ ഇൻപുട്ടായി എടുക്കുകയും ആ ഫംഗ്ഷന്റെ മെമ്മോയിസ്ഡ് പതിപ്പ് തിരികെ നൽകുകയും ചെയ്യുന്നു, ഫലങ്ങൾ ക്ലാസ്സിന്റെ `cache`-ൽ സംഭരിക്കുന്നു.
- ഇത് ഒരു ക്ലാസ്സിലെ നിർദ്ദിഷ്ട മെത്തേഡുകൾ തിരഞ്ഞെടുത്ത് മെമ്മോയിസ് ചെയ്യാൻ നിങ്ങളെ അനുവദിക്കുന്നു.
കാഷിംഗ് തന്ത്രങ്ങൾ
അടിസ്ഥാന മെമ്മോയിസേഷൻ പാറ്റേണുകൾക്ക് പുറമേ, കാഷെയുടെ സ്വഭാവം ഒപ്റ്റിമൈസ് ചെയ്യാനും അതിന്റെ വലുപ്പം നിയന്ത്രിക്കാനും വ്യത്യസ്ത കാഷിംഗ് തന്ത്രങ്ങൾ ഉപയോഗിക്കാം. കാഷെ കാര്യക്ഷമമായി തുടരുന്നുവെന്നും അമിതമായ മെമ്മറി ഉപയോഗിക്കുന്നില്ലെന്നും ഈ തന്ത്രങ്ങൾ ഉറപ്പാക്കാൻ സഹായിക്കുന്നു.
1. ലീസ്റ്റ് റീസന്റ്ലി യൂസ്ഡ് (LRU) കാഷെ
കാഷെ അതിന്റെ പരമാവധി വലുപ്പത്തിൽ എത്തുമ്പോൾ, ഏറ്റവും കുറഞ്ഞ സമയം മുൻപ് ഉപയോഗിച്ച ഇനങ്ങൾ LRU കാഷെ ഒഴിവാക്കുന്നു. ഈ തന്ത്രം ഏറ്റവും കൂടുതൽ ഉപയോഗിക്കുന്ന ഡാറ്റ കാഷെയിൽ നിലനിൽക്കുന്നുവെന്നും, കുറഞ്ഞ ഉപയോഗമുള്ള ഡാറ്റ ഉപേക്ഷിക്കപ്പെടുന്നുവെന്നും ഉറപ്പാക്കുന്നു.
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (this.cache.has(key)) {
const value = this.cache.get(key);
this.cache.delete(key); // Re-insert to mark as recently used
this.cache.set(key, value);
return value;
} else {
return undefined;
}
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
}
this.cache.set(key, value);
if (this.cache.size > this.capacity) {
// Remove the least recently used item
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
}
}
// Example usage:
const lruCache = new LRUCache(3); // Capacity of 3
lruCache.put('a', 1);
lruCache.put('b', 2);
lruCache.put('c', 3);
console.log(lruCache.get('a')); // 1 (moves 'a' to the end)
lruCache.put('d', 4); // 'b' is evicted
console.log(lruCache.get('b')); // undefined
console.log(lruCache.get('a')); // 1
console.log(lruCache.get('c')); // 3
console.log(lruCache.get('d')); // 4
വിശദീകരണം:
- ഇൻസേർഷൻ ഓർഡർ നിലനിർത്തുന്ന `Map` കാഷെ സംഭരിക്കാൻ ഉപയോഗിക്കുന്നു.
- `get(key)` മൂല്യം വീണ്ടെടുക്കുകയും, അടുത്തിടെ ഉപയോഗിച്ചതായി അടയാളപ്പെടുത്തുന്നതിന് കീ-വാല്യൂ ജോഡി വീണ്ടും ചേർക്കുകയും ചെയ്യുന്നു.
- `put(key, value)` കീ-വാല്യൂ ജോഡി ചേർക്കുന്നു. കാഷെ നിറഞ്ഞിട്ടുണ്ടെങ്കിൽ, ഏറ്റവും കുറഞ്ഞ സമയം മുൻപ് ഉപയോഗിച്ച ഇനം (`Map`-ലെ ആദ്യ ഇനം) നീക്കംചെയ്യുന്നു.
2. ലീസ്റ്റ് ഫ്രീക്വന്റ്ലി യൂസ്ഡ് (LFU) കാഷെ
കാഷെ നിറയുമ്പോൾ ഏറ്റവും കുറഞ്ഞ തവണ ഉപയോഗിച്ച ഇനങ്ങൾ LFU കാഷെ ഒഴിവാക്കുന്നു. ഈ തന്ത്രം കൂടുതൽ തവണ ഉപയോഗിക്കുന്ന ഡാറ്റയ്ക്ക് മുൻഗണന നൽകുന്നു, അത് കാഷെയിൽ തന്നെ നിലനിൽക്കുന്നുവെന്ന് ഉറപ്പാക്കുന്നു.
class LFUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
this.frequencies = new Map();
this.minFrequency = 0;
}
get(key) {
if (!this.cache.has(key)) {
return undefined;
}
const frequency = this.frequencies.get(key);
this.frequencies.set(key, frequency + 1);
return this.cache.get(key);
}
put(key, value) {
if (this.capacity <= 0) {
return;
}
if (this.cache.has(key)) {
this.cache.set(key, value);
this.get(key);
return;
}
if (this.cache.size >= this.capacity) {
this.evict();
}
this.cache.set(key, value);
this.frequencies.set(key, 1);
this.minFrequency = 1;
}
evict() {
let minFreq = Infinity;
for (const frequency of this.frequencies.values()) {
minFreq = Math.min(minFreq, frequency);
}
const keysToRemove = [];
this.frequencies.forEach((freq, key) => {
if (freq === minFreq) {
keysToRemove.push(key);
}
});
const keyToRemove = keysToRemove[0];
this.cache.delete(keyToRemove);
this.frequencies.delete(keyToRemove);
}
}
// Example usage:
const lfuCache = new LFUCache(2);
lfuCache.put('a', 1);
lfuCache.put('b', 2);
console.log(lfuCache.get('a')); // 1, frequency(a) = 2
lfuCache.put('c', 3); // evicts 'b' because frequency(b) = 1
console.log(lfuCache.get('b')); // undefined
console.log(lfuCache.get('a')); // 1, frequency(a) = 3
console.log(lfuCache.get('c')); // 3, frequency(c) = 2
വിശദീകരണം:
- രണ്ട് `Map` ഒബ്ജക്റ്റുകൾ ഉപയോഗിക്കുന്നു: കീ-വാല്യൂ ജോഡികൾ സംഭരിക്കുന്നതിന് `cache`, ഓരോ കീയുടെയും ആക്സസ് ഫ്രീക്വൻസി സംഭരിക്കുന്നതിന് `frequencies`.
- `get(key)` മൂല്യം വീണ്ടെടുക്കുകയും ഫ്രീക്വൻസി കൗണ്ട് വർദ്ധിപ്പിക്കുകയും ചെയ്യുന്നു.
- `put(key, value)` കീ-വാല്യൂ ജോഡി ചേർക്കുന്നു. കാഷെ നിറഞ്ഞിട്ടുണ്ടെങ്കിൽ, അത് ഏറ്റവും കുറഞ്ഞ തവണ ഉപയോഗിച്ച ഇനം ഒഴിവാക്കുന്നു.
- `evict()` ഏറ്റവും കുറഞ്ഞ ഫ്രീക്വൻസി കൗണ്ട് കണ്ടെത്തുകയും അതിനനുസരിച്ചുള്ള കീ-വാല്യൂ ജോഡി `cache`, `frequencies` എന്നിവയിൽ നിന്ന് നീക്കം ചെയ്യുകയും ചെയ്യുന്നു.
3. സമയ-അടിസ്ഥാനത്തിലുള്ള എക്സ്പൈറേഷൻ
ഈ തന്ത്രം ഒരു നിശ്ചിത സമയത്തിന് ശേഷം കാഷെ ചെയ്ത ഇനങ്ങൾ അസാധുവാക്കുന്നു. കാലക്രമേണ കാലഹരണപ്പെടുന്ന ഡാറ്റയ്ക്ക് ഇത് ഉപയോഗപ്രദമാണ്. ഉദാഹരണത്തിന്, കുറച്ച് മിനിറ്റത്തേക്ക് മാത്രം സാധുതയുള്ള API പ്രതികരണങ്ങൾ കാഷെ ചെയ്യുന്നത്.
function memoizeWithExpiration(func, ttl) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && cached.expiry > Date.now()) {
return cached.value;
} else {
const result = func.apply(this, args);
cache.set(key, { value: result, expiry: Date.now() + ttl });
return result;
}
};
}
// Example: Memoizing a function with a 5-second expiration time
function getDataFromAPI(endpoint) {
console.log(`Fetching data from ${endpoint}...`);
// Simulate an API call with a delay
return new Promise(resolve => {
setTimeout(() => {
resolve(`Data from ${endpoint}`);
}, 1000);
});
}
const memoizedGetData = memoizeWithExpiration(getDataFromAPI, 5000); // TTL: 5 seconds
async function testExpiration() {
console.log(await memoizedGetData('/users')); // Fetches and caches
console.log(await memoizedGetData('/users')); // Retrieves from cache
setTimeout(async () => {
console.log(await memoizedGetData('/users')); // Fetches again after 5 seconds
}, 6000);
}
testExpiration();
വിശദീകരണം:
- `memoizeWithExpiration` ഫംഗ്ഷൻ `func` എന്ന ഫംഗ്ഷനും മില്ലിസെക്കൻഡിൽ ഒരു ടൈം-ടു-ലൈവ് (TTL) മൂല്യവും ഇൻപുട്ടായി എടുക്കുന്നു.
- ഇത് ഒരു എക്സ്പൈറി ടൈംസ്റ്റാമ്പിനൊപ്പം കാഷെ ചെയ്ത മൂല്യം സംഭരിക്കുന്നു.
- കാഷെ ചെയ്ത ഒരു മൂല്യം തിരികെ നൽകുന്നതിന് മുമ്പ്, എക്സ്പൈറി ടൈംസ്റ്റാമ്പ് ഇപ്പോഴും ഭാവിയിലാണോ എന്ന് അത് പരിശോധിക്കുന്നു. അല്ലെങ്കിൽ, അത് കാഷെ അസാധുവാക്കുകയും ഡാറ്റ വീണ്ടും ലഭ്യമാക്കുകയും ചെയ്യുന്നു.
പ്രകടന മികവും പരിഗണനകളും
ഒരേ ഇൻപുട്ടുകൾ ഉപയോഗിച്ച് ആവർത്തിച്ച് വിളിക്കുന്ന, കമ്പ്യൂട്ടേഷണലായി സമയം എടുക്കുന്ന ഫംഗ്ഷനുകൾക്ക് മെമ്മോയിസേഷന് പ്രകടനം ഗണ്യമായി മെച്ചപ്പെടുത്താൻ കഴിയും. താഴെ പറയുന്ന സാഹചര്യങ്ങളിൽ പ്രകടന നേട്ടങ്ങൾ ഏറ്റവും പ്രകടമാണ്:
- റിക്കേഴ്സീവ് ഫംഗ്ഷനുകൾ: മെമ്മോയിസേഷന് റിക്കേഴ്സീവ് കോളുകളുടെ എണ്ണം ഗണ്യമായി കുറയ്ക്കാൻ കഴിയും, ഇത് എക്സ്പോണൻഷ്യൽ പ്രകടന മെച്ചപ്പെടുത്തലുകളിലേക്ക് നയിക്കുന്നു.
- ഓവർലാപ്പിംഗ് സബ്പ്രോബ്ലംസ് ഉള്ള ഫംഗ്ഷനുകൾ: സബ്പ്രോബ്ലംസിന്റെ ഫലങ്ങൾ സംഭരിക്കുകയും ആവശ്യമുള്ളപ്പോൾ അവ പുനരുപയോഗിക്കുകയും ചെയ്യുന്നതിലൂടെ മെമ്മോയിസേഷന് അനാവശ്യമായ കണക്കുകൂട്ടലുകൾ ഒഴിവാക്കാനാകും.
- ഒരേപോലെയുള്ള ഇൻപുട്ടുകൾ പതിവായി വരുന്ന ഫംഗ്ഷനുകൾ: ഓരോ യുണീക്ക് ഇൻപുട്ടുകൾക്കും ഫംഗ്ഷൻ ഒരു തവണ മാത്രമേ എക്സിക്യൂട്ട് ചെയ്യപ്പെടുന്നുള്ളൂ എന്ന് മെമ്മോയിസേഷൻ ഉറപ്പാക്കുന്നു.
എങ്കിലും, മെമ്മോയിസേഷൻ ഉപയോഗിക്കുമ്പോൾ താഴെ പറയുന്ന കാര്യങ്ങൾ പരിഗണിക്കേണ്ടത് പ്രധാനമാണ്:
- മെമ്മറി ഉപയോഗം: ഫംഗ്ഷൻ കോളുകളുടെ ഫലങ്ങൾ സംഭരിക്കുന്നതിനാൽ മെമ്മോയിസേഷൻ മെമ്മറി ഉപയോഗം വർദ്ധിപ്പിക്കുന്നു. ധാരാളം ഇൻപുട്ടുകൾ സാധ്യമായ ഫംഗ്ഷനുകൾക്കോ പരിമിതമായ മെമ്മറി വിഭവങ്ങളുള്ള ആപ്ലിക്കേഷനുകൾക്കോ ഇത് ഒരു പ്രശ്നമാകാം.
- കാഷെ ഇൻവാലിഡേഷൻ: അടിസ്ഥാന ഡാറ്റ മാറുകയാണെങ്കിൽ, കാഷെ ചെയ്ത ഫലങ്ങൾ കാലഹരണപ്പെട്ടേക്കാം. കാഷെ ഡാറ്റയുമായി പൊരുത്തപ്പെടുന്നുവെന്ന് ഉറപ്പാക്കാൻ ഒരു കാഷെ ഇൻവാലിഡേഷൻ തന്ത്രം നടപ്പിലാക്കേണ്ടത് അത്യാവശ്യമാണ്.
- സങ്കീർണ്ണത: മെമ്മോയിസേഷൻ നടപ്പിലാക്കുന്നത് കോഡിൽ സങ്കീർണ്ണത വർദ്ധിപ്പിക്കും, പ്രത്യേകിച്ചും സങ്കീർണ്ണമായ കാഷിംഗ് തന്ത്രങ്ങൾക്ക്. മെമ്മോയിസേഷൻ ഉപയോഗിക്കുന്നതിന് മുമ്പ് കോഡിന്റെ സങ്കീർണ്ണതയും പരിപാലനക്ഷമതയും ശ്രദ്ധാപൂർവ്വം പരിഗണിക്കേണ്ടത് പ്രധാനമാണ്.
പ്രായോഗിക ഉദാഹരണങ്ങളും ഉപയോഗ സാഹചര്യങ്ങളും
പ്രകടനം ഒപ്റ്റിമൈസ് ചെയ്യുന്നതിന് മെമ്മോയിസേഷൻ വിപുലമായ സാഹചര്യങ്ങളിൽ പ്രയോഗിക്കാൻ കഴിയും. ചില പ്രായോഗിക ഉദാഹരണങ്ങൾ ഇതാ:
- ഫ്രണ്ട്-എൻഡ് വെബ് ഡെവലപ്മെന്റ്: ജാവാസ്ക്രിപ്റ്റിലെ സമയം എടുക്കുന്ന കണക്കുകൂട്ടലുകൾ മെമ്മോയിസ് ചെയ്യുന്നത് വെബ് ആപ്ലിക്കേഷനുകളുടെ പ്രതികരണശേഷി മെച്ചപ്പെടുത്തും. ഉദാഹരണത്തിന്, സങ്കീർണ്ണമായ DOM മാനിപ്പുലേഷനുകൾ നടത്തുന്ന ഫംഗ്ഷനുകളോ ലേയൗട്ട് പ്രോപ്പർട്ടികൾ കണക്കാക്കുന്ന ഫംഗ്ഷനുകളോ നിങ്ങൾക്ക് മെമ്മോയിസ് ചെയ്യാം.
- സെർവർ-സൈഡ് ആപ്ലിക്കേഷനുകൾ: ഡാറ്റാബേസ് ക്വറികളുടെയോ API കോളുകളുടെയോ ഫലങ്ങൾ കാഷെ ചെയ്യുന്നതിനും, സെർവറിലെ ലോഡ് കുറയ്ക്കുന്നതിനും പ്രതികരണ സമയം മെച്ചപ്പെടുത്തുന്നതിനും മെമ്മോയിസേഷൻ ഉപയോഗിക്കാം.
- ഡാറ്റാ അനാലിസിസ്: ഇടയ്ക്കുള്ള കണക്കുകൂട്ടലുകളുടെ ഫലങ്ങൾ കാഷെ ചെയ്യുന്നതിലൂടെ ഡാറ്റാ അനാലിസിസ് ജോലികൾ വേഗത്തിലാക്കാൻ മെമ്മോയിസേഷന് കഴിയും. ഉദാഹരണത്തിന്, സ്റ്റാറ്റിസ്റ്റിക്കൽ അനാലിസിസ് അല്ലെങ്കിൽ മെഷീൻ ലേണിംഗ് അൽഗോരിതങ്ങൾ നടത്തുന്ന ഫംഗ്ഷനുകൾ നിങ്ങൾക്ക് മെമ്മോയിസ് ചെയ്യാം.
- ഗെയിം ഡെവലപ്മെന്റ്: കൊളിഷൻ ഡിറ്റക്ഷൻ അല്ലെങ്കിൽ പാത്ത്ഫൈൻഡിംഗ് പോലുള്ള പതിവായി ഉപയോഗിക്കുന്ന കണക്കുകൂട്ടലുകളുടെ ഫലങ്ങൾ കാഷെ ചെയ്യുന്നതിലൂടെ ഗെയിം പ്രകടനം ഒപ്റ്റിമൈസ് ചെയ്യാൻ മെമ്മോയിസേഷൻ ഉപയോഗിക്കാം.
ഉപസംഹാരം
ജാവാസ്ക്രിപ്റ്റ് ആപ്ലിക്കേഷനുകളുടെ പ്രകടനം ഗണ്യമായി മെച്ചപ്പെടുത്താൻ കഴിയുന്ന ഒരു ശക്തമായ ഒപ്റ്റിമൈസേഷൻ സാങ്കേതികതയാണ് മെമ്മോയിസേഷൻ. സമയം എടുക്കുന്ന ഫംഗ്ഷൻ കോളുകളുടെ ഫലങ്ങൾ കാഷെ ചെയ്യുന്നതിലൂടെ, നിങ്ങൾക്ക് അനാവശ്യമായ കണക്കുകൂട്ടലുകൾ ഒഴിവാക്കാനും എക്സിക്യൂഷൻ സമയം കുറയ്ക്കാനും കഴിയും. എന്നിരുന്നാലും, പ്രകടന നേട്ടങ്ങളും മെമ്മറി ഉപയോഗം, കാഷെ ഇൻവാലിഡേഷൻ, കോഡ് സങ്കീർണ്ണത എന്നിവയും തമ്മിലുള്ള കാര്യങ്ങൾ ശ്രദ്ധാപൂർവ്വം പരിഗണിക്കേണ്ടത് പ്രധാനമാണ്. വിവിധ മെമ്മോയിസേഷൻ പാറ്റേണുകളും കാഷിംഗ് തന്ത്രങ്ങളും മനസ്സിലാക്കുന്നതിലൂടെ, നിങ്ങളുടെ ജാവാസ്ക്രിപ്റ്റ് കോഡ് ഒപ്റ്റിമൈസ് ചെയ്യാനും ഉയർന്ന പ്രകടനമുള്ള ആപ്ലിക്കേഷനുകൾ നിർമ്മിക്കാനും നിങ്ങൾക്ക് മെമ്മോയിസേഷൻ ഫലപ്രദമായി പ്രയോഗിക്കാൻ കഴിയും.