ജാവയുടെ ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്കിനെക്കുറിച്ചുള്ള സമഗ്രമായ ഈ ഗൈഡിലൂടെ പാരലൽ പ്രോസസ്സിംഗിന്റെ ശക്തി പ്രയോജനപ്പെടുത്തുക. നിങ്ങളുടെ ആഗോള ആപ്ലിക്കേഷനുകളിൽ മികച്ച പ്രകടനത്തിനായി ടാസ്ക്കുകൾ എങ്ങനെ വിഭജിക്കാം, നടപ്പിലാക്കാം, സംയോജിപ്പിക്കാം എന്ന് പഠിക്കുക.
സമാന്തര ടാസ്ക് എക്സിക്യൂഷനിൽ വൈദഗ്ദ്ധ്യം നേടാം: ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്കിന്റെ ആഴത്തിലുള്ള ഒരു വിലയിരുത്തൽ
ഡാറ്റയെ അടിസ്ഥാനമാക്കി ആഗോളതലത്തിൽ പരസ്പരം ബന്ധപ്പെട്ടിരിക്കുന്ന ഇന്നത്തെ ലോകത്ത്, കാര്യക്ഷമവും വേഗതയേറിയതുമായ ആപ്ലിക്കേഷനുകൾക്ക് വലിയ ആവശ്യകതയുണ്ട്. ആധുനിക സോഫ്റ്റ്വെയറുകൾക്ക് പലപ്പോഴും വലിയ അളവിലുള്ള ഡാറ്റ പ്രോസസ്സ് ചെയ്യേണ്ടതും, സങ്കീർണ്ണമായ കണക്കുകൂട്ടലുകൾ നടത്തേണ്ടതും, ഒരേസമയം നിരവധി പ്രവർത്തനങ്ങൾ കൈകാര്യം ചെയ്യേണ്ടതും ആവശ്യമാണ്. ഈ വെല്ലുവിളികളെ നേരിടാൻ, ഡെവലപ്പർമാർ സമാന്തര പ്രോസസ്സിംഗിലേക്ക് (parallel processing) കൂടുതലായി തിരിഞ്ഞിരിക്കുന്നു - ഒരു വലിയ പ്രശ്നത്തെ ചെറുതും കൈകാര്യം ചെയ്യാൻ കഴിയുന്നതുമായ ഉപ-പ്രശ്നങ്ങളായി വിഭജിച്ച് ഒരേസമയം പരിഹരിക്കുന്ന രീതിയാണിത്. ജാവയുടെ കൺകറൻസി യൂട്ടിലിറ്റികളിൽ മുൻപന്തിയിൽ നിൽക്കുന്ന ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക്, സമാന്തര ടാസ്ക്കുകൾ ലളിതമാക്കാനും മികച്ച രീതിയിൽ നടപ്പിലാക്കാനും രൂപകൽപ്പന ചെയ്തിട്ടുള്ള ഒരു ശക്തമായ ഉപകരണമാണ്, പ്രത്യേകിച്ച് കമ്പ്യൂട്ട്-ഇന്റെൻസീവ് ആയതും വിഭജിച്ച് കീഴടക്കുക (divide-and-conquer) എന്ന തന്ത്രത്തിന് അനുയോജ്യമായതുമായ ടാസ്ക്കുകൾക്ക്.
സമാന്തരതയുടെ (Parallelism) ആവശ്യകത മനസ്സിലാക്കാം
ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്കിന്റെ വിശദാംശങ്ങളിലേക്ക് കടക്കുന്നതിന് മുൻപ്, എന്തുകൊണ്ടാണ് സമാന്തര പ്രോസസ്സിംഗ് ഇത്രയധികം അത്യാവശ്യമാകുന്നതെന്ന് മനസ്സിലാക്കേണ്ടത് പ്രധാനമാണ്. പരമ്പരാഗതമായി, ആപ്ലിക്കേഷനുകൾ ടാസ്ക്കുകൾ ഒന്നിനുപുറകെ ഒന്നായി ക്രമാനുഗതമായാണ് (sequentially) നിർവഹിച്ചിരുന്നത്. ഈ സമീപനം ലളിതമാണെങ്കിലും, ആധുനിക കമ്പ്യൂട്ടേഷണൽ ആവശ്യകതകൾ കൈകാര്യം ചെയ്യുമ്പോൾ ഇതൊരു തടസ്സമായി മാറുന്നു. ദശലക്ഷക്കണക്കിന് ഇടപാടുകൾ പ്രോസസ്സ് ചെയ്യേണ്ട, വിവിധ പ്രദേശങ്ങളിൽ നിന്നുള്ള ഉപയോക്തൃ പെരുമാറ്റ ഡാറ്റ വിശകലനം ചെയ്യേണ്ട, അല്ലെങ്കിൽ സങ്കീർണ്ണമായ വിഷ്വൽ ഇന്റർഫേസുകൾ തത്സമയം റെൻഡർ ചെയ്യേണ്ട ഒരു ആഗോള ഇ-കൊമേഴ്സ് പ്ലാറ്റ്ഫോം പരിഗണിക്കുക. ഒരു സിംഗിൾ-ത്രെഡ് എക്സിക്യൂഷൻ വളരെ വേഗത കുറഞ്ഞതായിരിക്കും, ഇത് മോശം ഉപയോക്തൃ അനുഭവങ്ങൾക്കും ബിസിനസ്സ് അവസരങ്ങൾ നഷ്ടപ്പെടുന്നതിനും കാരണമാകും.
മൊബൈൽ ഫോണുകൾ മുതൽ വലിയ സർവർ ക്ലസ്റ്ററുകൾ വരെ മിക്ക കമ്പ്യൂട്ടിംഗ് ഉപകരണങ്ങളിലും മൾട്ടി-കോർ പ്രോസസ്സറുകൾ ഇപ്പോൾ സാധാരണമാണ്. സമാന്തരത ഈ ഒന്നിലധികം കോറുകളുടെ ശക്തി പ്രയോജനപ്പെടുത്താൻ നമ്മെ അനുവദിക്കുന്നു, ഇത് ആപ്ലിക്കേഷനുകൾക്ക് ഒരേ സമയം കൊണ്ട് കൂടുതൽ ജോലികൾ ചെയ്യാൻ പ്രാപ്തമാക്കുന്നു. ഇത് താഴെ പറയുന്ന കാര്യങ്ങളിലേക്ക് നയിക്കുന്നു:
- മെച്ചപ്പെട്ട പ്രകടനം: ടാസ്ക്കുകൾ വളരെ വേഗത്തിൽ പൂർത്തിയാകുന്നു, ഇത് കൂടുതൽ വേഗതയുള്ള ആപ്ലിക്കേഷനിലേക്ക് നയിക്കുന്നു.
- വർദ്ധിച്ച ത്രൂപുട്ട്: ഒരു നിശ്ചിത സമയത്തിനുള്ളിൽ കൂടുതൽ പ്രവർത്തനങ്ങൾ പ്രോസസ്സ് ചെയ്യാൻ കഴിയും.
- മെച്ചപ്പെട്ട റിസോഴ്സ് വിനിയോഗം: ലഭ്യമായ എല്ലാ പ്രോസസ്സിംഗ് കോറുകളും പ്രയോജനപ്പെടുത്തുന്നത് റിസോഴ്സുകൾ വെറുതെയിരിക്കുന്നത് തടയുന്നു.
- സ്കേലബിലിറ്റി: കൂടുതൽ പ്രോസസ്സിംഗ് പവർ ഉപയോഗിച്ച് വർദ്ധിച്ചുവരുന്ന വർക്ക്ലോഡുകൾ കൈകാര്യം ചെയ്യാൻ ആപ്ലിക്കേഷനുകൾക്ക് കൂടുതൽ ഫലപ്രദമായി സ്കെയിൽ ചെയ്യാൻ കഴിയും.
വിഭജിച്ച് കീഴടക്കുക (Divide-and-Conquer) എന്ന തന്ത്രം
സ്ഥാപിതമായ വിഭജിച്ച് കീഴടക്കുക (divide-and-conquer) എന്ന അൽഗോരിതം തന്ത്രത്തെ അടിസ്ഥാനമാക്കിയാണ് ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക് നിർമ്മിച്ചിരിക്കുന്നത്. ഈ സമീപനത്തിൽ ഉൾപ്പെടുന്നവ:
- വിഭജിക്കുക (Divide): ഒരു സങ്കീർണ്ണമായ പ്രശ്നത്തെ ചെറുതും സ്വതന്ത്രവുമായ ഉപ-പ്രശ്നങ്ങളായി വിഭജിക്കുക.
- കീഴടക്കുക (Conquer): ഈ ഉപ-പ്രശ്നങ്ങളെ റിക്കേഴ്സീവ് ആയി പരിഹരിക്കുക. ഒരു ഉപ-പ്രശ്നം വളരെ ചെറുതാണെങ്കിൽ, അത് നേരിട്ട് പരിഹരിക്കും. അല്ലാത്തപക്ഷം, അത് വീണ്ടും വിഭജിക്കപ്പെടും.
- സംയോജിപ്പിക്കുക (Combine): യഥാർത്ഥ പ്രശ്നത്തിനുള്ള പരിഹാരം രൂപീകരിക്കുന്നതിന് ഉപ-പ്രശ്നങ്ങളുടെ പരിഹാരങ്ങളെ സംയോജിപ്പിക്കുക.
ഈ റിക്കേഴ്സീവ് സ്വഭാവം ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്കിനെ താഴെ പറയുന്ന പോലുള്ള ടാസ്ക്കുകൾക്ക് പ്രത്യേകിച്ചും അനുയോജ്യമാക്കുന്നു:
- അറേ പ്രോസസ്സിംഗ് (ഉദാ. സോർട്ടിംഗ്, സെർച്ചിംഗ്, ട്രാൻസ്ഫോർമേഷൻസ്)
- മാട്രിക്സ് പ്രവർത്തനങ്ങൾ
- ഇമേജ് പ്രോസസ്സിംഗും മാനിപ്പുലേഷനും
- ഡാറ്റാ അഗ്രഗേഷനും വിശകലനവും
- ഫിബൊനാച്ചി സീക്വൻസ് കണക്കുകൂട്ടൽ അല്ലെങ്കിൽ ട്രീ ട്രാവേഴ്സലുകൾ പോലുള്ള റിക്കേഴ്സീവ് അൽഗോരിതങ്ങൾ
ജാവയിലെ ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക്: ഒരു ആമുഖം
ജാവ 7-ൽ അവതരിപ്പിച്ച ജാവയുടെ ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക്, വിഭജിച്ച് കീഴടക്കുക എന്ന തന്ത്രത്തെ അടിസ്ഥാനമാക്കി സമാന്തര അൽഗോരിതങ്ങൾ നടപ്പിലാക്കുന്നതിനുള്ള ഒരു ചിട്ടയായ മാർഗ്ഗം നൽകുന്നു. ഇതിൽ പ്രധാനമായും രണ്ട് അബ്സ്ട്രാക്ട് ക്ലാസുകളാണുള്ളത്:
RecursiveTask<V>
: ഒരു ഫലം നൽകുന്ന ടാസ്ക്കുകൾക്കായി.RecursiveAction
: ഒരു ഫലവും നൽകാത്ത ടാസ്ക്കുകൾക്കായി.
ഈ ക്ലാസുകൾ ForkJoinPool
എന്ന ഒരു പ്രത്യേകതരം ExecutorService
-നൊപ്പം ഉപയോഗിക്കാൻ രൂപകൽപ്പന ചെയ്തിട്ടുള്ളതാണ്. ForkJoinPool
ഫോർക്ക്-ജോയിൻ ടാസ്ക്കുകൾക്കായി ഒപ്റ്റിമൈസ് ചെയ്തിരിക്കുന്നു, കൂടാതെ അതിന്റെ കാര്യക്ഷമതയുടെ താക്കോലായ വർക്ക്-സ്റ്റീലിംഗ് എന്ന ഒരു സാങ്കേതികവിദ്യ ഉപയോഗിക്കുന്നു.
ഫ്രെയിംവർക്കിലെ പ്രധാന ഘടകങ്ങൾ
ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്കുമായി പ്രവർത്തിക്കുമ്പോൾ നിങ്ങൾ കണ്ടുമുട്ടുന്ന പ്രധാന ഘടകങ്ങളെക്കുറിച്ച് വിശദമായി നോക്കാം:
1. ForkJoinPool
ForkJoinPool
ആണ് ഈ ഫ്രെയിംവർക്കിന്റെ ഹൃദയം. ഇത് ടാസ്ക്കുകൾ നിർവഹിക്കുന്ന വർക്കർ ത്രെഡുകളുടെ ഒരു പൂൾ കൈകാര്യം ചെയ്യുന്നു. പരമ്പരാഗത ത്രെഡ് പൂളുകളിൽ നിന്ന് വ്യത്യസ്തമായി, ForkJoinPool
പ്രത്യേകമായി ഫോർക്ക്-ജോയിൻ മോഡലിനായി രൂപകൽപ്പന ചെയ്തിട്ടുള്ളതാണ്. ഇതിന്റെ പ്രധാന സവിശേഷതകളിൽ ഇവ ഉൾപ്പെടുന്നു:
- വർക്ക്-സ്റ്റീലിംഗ് (Work-Stealing): ഇത് ഒരു നിർണ്ണായകമായ ഒപ്റ്റിമൈസേഷനാണ്. ഒരു വർക്കർ ത്രെഡ് അതിന് നൽകിയിട്ടുള്ള ടാസ്ക്കുകൾ പൂർത്തിയാക്കുമ്പോൾ, അത് വെറുതെയിരിക്കില്ല. പകരം, തിരക്കുള്ള മറ്റ് വർക്കർ ത്രെഡുകളുടെ ക്യൂവിൽ നിന്ന് ടാസ്ക്കുകൾ "മോഷ്ടിക്കുന്നു". ഇത് ലഭ്യമായ എല്ലാ പ്രോസസ്സിംഗ് പവറും ഫലപ്രദമായി ഉപയോഗിക്കുന്നുവെന്ന് ഉറപ്പാക്കുന്നു, വെറുതെയിരിക്കുന്ന സമയം കുറയ്ക്കുകയും ത്രൂപുട്ട് വർദ്ധിപ്പിക്കുകയും ചെയ്യുന്നു. ഒരു വലിയ പ്രോജക്റ്റിൽ ഒരു ടീം പ്രവർത്തിക്കുന്നുവെന്ന് സങ്കൽപ്പിക്കുക; ഒരാൾ തന്റെ ഭാഗം നേരത്തെ പൂർത്തിയാക്കുകയാണെങ്കിൽ, ജോലിഭാരം കൂടുതലുള്ള മറ്റൊരാളിൽ നിന്ന് അവർക്ക് ജോലി ഏറ്റെടുക്കാൻ കഴിയും.
- നിയന്ത്രിത നിർവ്വഹണം (Managed Execution): ഈ പൂൾ ത്രെഡുകളുടെയും ടാസ്ക്കുകളുടെയും ലൈഫ് സൈക്കിൾ കൈകാര്യം ചെയ്യുന്നു, ഇത് കൺകറന്റ് പ്രോഗ്രാമിംഗ് ലളിതമാക്കുന്നു.
- ക്രമീകരിക്കാവുന്ന ഫെയർനസ് (Pluggable Fairness): ടാസ്ക് ഷെഡ്യൂളിംഗിൽ വിവിധ തലത്തിലുള്ള ന്യായമായ പരിഗണനയ്ക്കായി ഇത് കോൺഫിഗർ ചെയ്യാൻ കഴിയും.
നിങ്ങൾക്ക് ഇതുപോലെ ഒരു ForkJoinPool
ഉണ്ടാക്കാം:
// കോമൺ പൂൾ ഉപയോഗിക്കുന്നു (മിക്ക സാഹചര്യങ്ങളിലും ശുപാർശ ചെയ്യുന്നത്)
ForkJoinPool pool = ForkJoinPool.commonPool();
// അല്ലെങ്കിൽ ഒരു കസ്റ്റം പൂൾ ഉണ്ടാക്കുന്നു
// ForkJoinPool customPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
commonPool()
എന്നത് ഒരു സ്റ്റാറ്റിക്, ഷെയർഡ് പൂൾ ആണ്, അത് നിങ്ങൾ സ്വന്തമായി ഉണ്ടാക്കുകയും കൈകാര്യം ചെയ്യുകയും ചെയ്യാതെ ഉപയോഗിക്കാൻ കഴിയും. ഇത് പലപ്പോഴും ലഭ്യമായ പ്രോസസ്സറുകളുടെ എണ്ണത്തെ അടിസ്ഥാനമാക്കി യുക്തിസഹമായ എണ്ണം ത്രെഡുകളോടെ മുൻകൂട്ടി കോൺഫിഗർ ചെയ്തിരിക്കും.
2. RecursiveTask<V>
RecursiveTask<V>
എന്നത് V
ടൈപ്പിലുള്ള ഒരു ഫലം കണക്കാക്കുന്ന ടാസ്ക്കിനെ പ്രതിനിധീകരിക്കുന്ന ഒരു അബ്സ്ട്രാക്ട് ക്ലാസ് ആണ്. ഇത് ഉപയോഗിക്കുന്നതിന്, നിങ്ങൾ ചെയ്യേണ്ടത്:
RecursiveTask<V>
ക്ലാസ് എക്സ്റ്റൻഡ് ചെയ്യുക.protected V compute()
എന്ന മെത്തേഡ് ഇംപ്ലിമെന്റ് ചെയ്യുക.
compute()
മെത്തേഡിനുള്ളിൽ, നിങ്ങൾ സാധാരണയായി ചെയ്യേണ്ടത്:
- ബേസ് കേസ് പരിശോധിക്കുക: ടാസ്ക് നേരിട്ട് കണക്കാക്കാൻ മാത്രം ചെറുതാണെങ്കിൽ, അങ്ങനെ ചെയ്ത് ഫലം നൽകുക.
- ഫോർക്ക് (Fork): ടാസ്ക് വളരെ വലുതാണെങ്കിൽ, അതിനെ ചെറിയ സബ്ടാസ്ക്കുകളായി വിഭജിക്കുക. ഈ സബ്ടാസ്ക്കുകൾക്കായി നിങ്ങളുടെ
RecursiveTask
-ന്റെ പുതിയ ഇൻസ്റ്റൻസുകൾ ഉണ്ടാക്കുക. ഒരു സബ്ടാസ്ക് അസിങ്ക്രണസ് ആയി എക്സിക്യൂഷനായി ഷെഡ്യൂൾ ചെയ്യാൻfork()
മെത്തേഡ് ഉപയോഗിക്കുക. - ജോയിൻ (Join): സബ്ടാസ്ക്കുകൾ ഫോർക്ക് ചെയ്ത ശേഷം, അവയുടെ ഫലങ്ങൾക്കായി നിങ്ങൾ കാത്തിരിക്കേണ്ടിവരും. ഫോർക്ക് ചെയ്ത ടാസ്ക്കിന്റെ ഫലം ലഭിക്കാൻ
join()
മെത്തേഡ് ഉപയോഗിക്കുക. ഈ മെത്തേഡ് ടാസ്ക് പൂർത്തിയാകുന്നതുവരെ ബ്ലോക്ക് ചെയ്യുന്നു. - സംയോജിപ്പിക്കുക (Combine): സബ്ടാസ്ക്കുകളിൽ നിന്നുള്ള ഫലങ്ങൾ ലഭിച്ചുകഴിഞ്ഞാൽ, നിലവിലെ ടാസ്ക്കിന്റെ അന്തിമഫലം ഉണ്ടാക്കാൻ അവയെ സംയോജിപ്പിക്കുക.
ഉദാഹരണം: ഒരു അറേയിലെ സംഖ്യകളുടെ തുക കണക്കാക്കുന്നു
ഒരു വലിയ അറേയിലെ എലമെന്റുകളുടെ തുക കാണുന്ന ഒരു ക്ലാസിക് ഉദാഹരണം നോക്കാം.
import java.util.concurrent.RecursiveTask;
public class SumArrayTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000; // വിഭജിക്കുന്നതിനുള്ള ത്രെഷോൾഡ്
private final int[] array;
private final int start;
private final int end;
public SumArrayTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
// ബേസ് കേസ്: സബ്-അറേ ചെറുതാണെങ്കിൽ, നേരിട്ട് തുക കാണുക
if (length <= THRESHOLD) {
return sequentialSum(array, start, end);
}
// റിക്കേഴ്സീവ് കേസ്: ടാസ്ക്കിനെ രണ്ട് സബ്-ടാസ്ക്കുകളായി വിഭജിക്കുക
int mid = start + length / 2;
SumArrayTask leftTask = new SumArrayTask(array, start, mid);
SumArrayTask rightTask = new SumArrayTask(array, mid, end);
// ഇടത് ടാസ്ക് ഫോർക്ക് ചെയ്യുക (എക്സിക്യൂഷനായി ഷെഡ്യൂൾ ചെയ്യുക)
leftTask.fork();
// വലത് ടാസ്ക് നേരിട്ട് കമ്പ്യൂട്ട് ചെയ്യുക (അല്ലെങ്കിൽ അതും ഫോർക്ക് ചെയ്യുക)
// ഇവിടെ, ഒരു ത്രെഡ് തിരക്കിലായിരിക്കാൻ ഞങ്ങൾ വലത് ടാസ്ക് നേരിട്ട് കമ്പ്യൂട്ട് ചെയ്യുന്നു
Long rightResult = rightTask.compute();
// ഇടത് ടാസ്ക് ജോയിൻ ചെയ്യുക (അതിന്റെ ഫലത്തിനായി കാത്തിരിക്കുക)
Long leftResult = leftTask.join();
// ഫലങ്ങൾ സംയോജിപ്പിക്കുക
return leftResult + rightResult;
}
private Long sequentialSum(int[] array, int start, int end) {
Long sum = 0L;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
public static void main(String[] args) {
int[] data = new int[1000000]; // ഉദാഹരണത്തിന് ഒരു വലിയ അറേ
for (int i = 0; i < data.length; i++) {
data[i] = i % 100;
}
ForkJoinPool pool = ForkJoinPool.commonPool();
SumArrayTask task = new SumArrayTask(data, 0, data.length);
System.out.println("Calculating sum...");
long startTime = System.nanoTime();
Long result = pool.invoke(task);
long endTime = System.nanoTime();
System.out.println("Sum: " + result);
System.out.println("Time taken: " + (endTime - startTime) / 1_000_000 + " ms");
// താരതമ്യത്തിന്, ഒരു സീക്വൻഷ്യൽ സം
// long sequentialResult = 0;
// for (int val : data) {
// sequentialResult += val;
// }
// System.out.println("Sequential Sum: " + sequentialResult);
}
}
ഈ ഉദാഹരണത്തിൽ:
THRESHOLD
ഒരു ടാസ്ക് എപ്പോൾ സീക്വൻഷ്യൽ ആയി പ്രോസസ്സ് ചെയ്യത്തക്കവിധം ചെറുതാണെന്ന് നിർണ്ണയിക്കുന്നു. അനുയോജ്യമായ ഒരു ത്രെഷോൾഡ് തിരഞ്ഞെടുക്കുന്നത് പ്രകടനത്തിന് നിർണ്ണായകമാണ്.- അറേയുടെ ഭാഗം വലുതാണെങ്കിൽ
compute()
ജോലി വിഭജിക്കുന്നു, ഒരു സബ്ടാസ്ക് ഫോർക്ക് ചെയ്യുന്നു, മറ്റൊന്ന് നേരിട്ട് കമ്പ്യൂട്ട് ചെയ്യുന്നു, എന്നിട്ട് ഫോർക്ക് ചെയ്ത ടാസ്കുമായി ജോയിൻ ചെയ്യുന്നു. invoke(task)
എന്നത്ForkJoinPool
-ലെ ഒരു സൗകര്യപ്രദമായ മെത്തേഡാണ്, അത് ഒരു ടാസ്ക് സമർപ്പിക്കുകയും അതിന്റെ പൂർത്തീകരണത്തിനായി കാത്തിരിക്കുകയും, അതിന്റെ ഫലം തിരികെ നൽകുകയും ചെയ്യുന്നു.
3. RecursiveAction
RecursiveAction
, RecursiveTask
-ന് സമാനമാണ്, പക്ഷേ ഇത് ഒരു റിട്ടേൺ മൂല്യം നൽകാത്ത ടാസ്ക്കുകൾക്കായി ഉപയോഗിക്കുന്നു. പ്രധാന ലോജിക് ഒന്നുതന്നെയാണ്: ടാസ്ക് വലുതാണെങ്കിൽ വിഭജിക്കുക, സബ്ടാസ്ക്കുകൾ ഫോർക്ക് ചെയ്യുക, തുടർന്ന് മുന്നോട്ട് പോകുന്നതിന് മുമ്പ് അവയുടെ പൂർത്തീകരണം ആവശ്യമാണെങ്കിൽ അവയെ ജോയിൻ ചെയ്യുക.
ഒരു RecursiveAction
നടപ്പിലാക്കാൻ, നിങ്ങൾ ചെയ്യേണ്ടത്:
RecursiveAction
എക്സ്റ്റൻഡ് ചെയ്യുക.protected void compute()
എന്ന മെത്തേഡ് ഇംപ്ലിമെന്റ് ചെയ്യുക.
compute()
എന്ന മെത്തേഡിനുള്ളിൽ, സബ്ടാസ്ക്കുകൾ ഷെഡ്യൂൾ ചെയ്യാൻ fork()
-ഉം അവയുടെ പൂർത്തീകരണത്തിനായി കാത്തിരിക്കാൻ join()
-ഉം ഉപയോഗിക്കും. ഒരു റിട്ടേൺ മൂല്യമില്ലാത്തതിനാൽ, നിങ്ങൾക്ക് ഫലങ്ങൾ "സംയോജിപ്പിക്കേണ്ട" ആവശ്യം വരാറില്ല, പക്ഷേ ആക്ഷൻ തന്നെ പൂർത്തിയാകുന്നതിന് മുമ്പ് ആശ്രിത സബ്ടാസ്ക്കുകളെല്ലാം പൂർത്തിയായി എന്ന് ഉറപ്പാക്കേണ്ടി വന്നേക്കാം.
ഉദാഹരണം: സമാന്തരമായി അറേ എലമെന്റ് ട്രാൻസ്ഫോർമേഷൻ
ഒരു അറേയിലെ ഓരോ എലമെന്റിനെയും സമാന്തരമായി രൂപാന്തരപ്പെടുത്തുന്നത് സങ്കൽപ്പിക്കുക, ഉദാഹരണത്തിന്, ഓരോ സംഖ്യയുടെയും വർഗ്ഗം കാണുന്നത്.
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.ForkJoinPool;
public class SquareArrayAction extends RecursiveAction {
private static final int THRESHOLD = 1000;
private final int[] array;
private final int start;
private final int end;
public SquareArrayAction(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected void compute() {
int length = end - start;
// ബേസ് കേസ്: സബ്-അറേ ചെറുതാണെങ്കിൽ, അത് സീക്വൻഷ്യൽ ആയി രൂപാന്തരപ്പെടുത്തുക
if (length <= THRESHOLD) {
sequentialSquare(array, start, end);
return; // തിരികെ നൽകാൻ ഫലമില്ല
}
// റിക്കേഴ്സീവ് കേസ്: ടാസ്ക് വിഭജിക്കുക
int mid = start + length / 2;
SquareArrayAction leftAction = new SquareArrayAction(array, start, mid);
SquareArrayAction rightAction = new SquareArrayAction(array, mid, end);
// രണ്ട് സബ്-ആക്ഷനുകളും ഫോർക്ക് ചെയ്യുക
// ഒന്നിലധികം ഫോർക്ക് ചെയ്ത ടാസ്ക്കുകൾക്ക് invokeAll ഉപയോഗിക്കുന്നത് പലപ്പോഴും കൂടുതൽ കാര്യക്ഷമമാണ്
invokeAll(leftAction, rightAction);
// invokeAll-ന് ശേഷം ഇടക്കാല ഫലങ്ങളെ ആശ്രയിക്കുന്നില്ലെങ്കിൽ പ്രത്യേകമായി ജോയിൻ ചെയ്യേണ്ടതില്ല
// നിങ്ങൾ ഓരോന്നായി ഫോർക്ക് ചെയ്യുകയും പിന്നീട് ജോയിൻ ചെയ്യുകയും ചെയ്യുകയാണെങ്കിൽ:
// leftAction.fork();
// rightAction.fork();
// leftAction.join();
// rightAction.join();
}
private void sequentialSquare(int[] array, int start, int end) {
for (int i = start; i < end; i++) {
array[i] = array[i] * array[i];
}
}
public static void main(String[] args) {
int[] data = new int[1000000];
for (int i = 0; i < data.length; i++) {
data[i] = (i % 50) + 1; // 1 മുതൽ 50 വരെയുള്ള മൂല്യങ്ങൾ
}
ForkJoinPool pool = ForkJoinPool.commonPool();
SquareArrayAction action = new SquareArrayAction(data, 0, data.length);
System.out.println("Squaring array elements...");
long startTime = System.nanoTime();
pool.invoke(action); // ആക്ഷനുകൾക്കുള്ള invoke() പൂർത്തീകരണത്തിനായി കാത്തിരിക്കുന്നു
long endTime = System.nanoTime();
System.out.println("Array transformation complete.");
System.out.println("Time taken: " + (endTime - startTime) / 1_000_000 + " ms");
// സ്ഥിരീകരിക്കുന്നതിനായി ആദ്യത്തെ കുറച്ച് എലമെന്റുകൾ പ്രിന്റ് ചെയ്യാം
// System.out.println("First 10 elements after squaring:");
// for (int i = 0; i < 10; i++) {
// System.out.print(data[i] + " ");
// }
// System.out.println();
}
}
ഇവിടെയുള്ള പ്രധാന കാര്യങ്ങൾ:
compute()
മെത്തേഡ് അറേയിലെ എലമെന്റുകളെ നേരിട്ട് മാറ്റം വരുത്തുന്നു.invokeAll(leftAction, rightAction)
എന്നത് രണ്ട് ടാസ്ക്കുകളും ഫോർക്ക് ചെയ്യുകയും പിന്നീട് അവയെ ജോയിൻ ചെയ്യുകയും ചെയ്യുന്ന ഒരു ഉപയോഗപ്രദമായ മെത്തേഡാണ്. ഓരോന്നായി ഫോർക്ക് ചെയ്ത് ജോയിൻ ചെയ്യുന്നതിനേക്കാൾ ഇത് പലപ്പോഴും കൂടുതൽ കാര്യക്ഷമമാണ്.
ഫോർക്ക്-ജോയിനിലെ നൂതന ആശയങ്ങളും മികച്ച രീതികളും
ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക് ശക്തമാണെങ്കിലും, അതിൽ വൈദഗ്ദ്ധ്യം നേടുന്നതിന് ചില സൂക്ഷ്മതകൾ കൂടി മനസ്സിലാക്കേണ്ടതുണ്ട്:
1. ശരിയായ ത്രെഷോൾഡ് തിരഞ്ഞെടുക്കൽ
THRESHOLD
നിർണ്ണായകമാണ്. അത് വളരെ കുറവാണെങ്കിൽ, ധാരാളം ചെറിയ ടാസ്ക്കുകൾ ഉണ്ടാക്കുന്നതിനും കൈകാര്യം ചെയ്യുന്നതിനും വളരെയധികം ഓവർഹെഡ് ഉണ്ടാകും. അത് വളരെ കൂടുതലാണെങ്കിൽ, നിങ്ങൾക്ക് ഒന്നിലധികം കോറുകൾ ഫലപ്രദമായി ഉപയോഗിക്കാൻ കഴിയില്ല, കൂടാതെ സമാന്തരതയുടെ പ്രയോജനങ്ങൾ കുറയുകയും ചെയ്യും. ഇതിന് സാർവത്രികമായ ഒരു മാന്ത്രിക സംഖ്യയില്ല; ഒപ്റ്റിമൽ ത്രെഷോൾഡ് പലപ്പോഴും നിർദ്ദിഷ്ട ടാസ്ക്, ഡാറ്റയുടെ വലുപ്പം, അടിസ്ഥാന ഹാർഡ്വെയർ എന്നിവയെ ആശ്രയിച്ചിരിക്കുന്നു. പരീക്ഷണം പ്രധാനമാണ്. സീക്വൻഷ്യൽ എക്സിക്യൂഷൻ ഏതാനും മില്ലിസെക്കൻഡ് എടുക്കുന്ന ഒരു മൂല്യം പലപ്പോഴും ഒരു നല്ല തുടക്കമാണ്.
2. അമിതമായ ഫോർക്കിംഗും ജോയിനിംഗും ഒഴിവാക്കുക
അடிக்கടിയുള്ളതും അനാവശ്യവുമായ ഫോർക്കിംഗും ജോയിനിംഗും പ്രകടനത്തകർച്ചയ്ക്ക് കാരണമാകും. ഓരോ fork()
കോളും പൂളിലേക്ക് ഒരു ടാസ്ക് ചേർക്കുന്നു, ഓരോ join()
-നും ഒരു ത്രെഡിനെ ബ്ലോക്ക് ചെയ്യാൻ സാധ്യതയുണ്ട്. എപ്പോൾ ഫോർക്ക് ചെയ്യണമെന്നും എപ്പോൾ നേരിട്ട് കമ്പ്യൂട്ട് ചെയ്യണമെന്നും തന്ത്രപരമായി തീരുമാനിക്കുക. SumArrayTask
ഉദാഹരണത്തിൽ കണ്ടതുപോലെ, ഒരു ബ്രാഞ്ച് നേരിട്ട് കമ്പ്യൂട്ട് ചെയ്യുമ്പോൾ മറ്റൊന്ന് ഫോർക്ക് ചെയ്യുന്നത് ത്രെഡുകളെ തിരക്കിലാക്കി നിർത്താൻ സഹായിക്കും.
3. invokeAll
ഉപയോഗിക്കുക
നിങ്ങൾക്ക് ഒന്നിലധികം സബ്ടാസ്ക്കുകൾ ഉണ്ടെങ്കിൽ, അവ സ്വതന്ത്രവും നിങ്ങൾ മുന്നോട്ട് പോകുന്നതിന് മുമ്പ് പൂർത്തിയാക്കേണ്ടതുമാണെങ്കിൽ, ഓരോ ടാസ്ക്കും സ്വമേധയാ ഫോർക്ക് ചെയ്യുന്നതിനും ജോയിൻ ചെയ്യുന്നതിനും പകരം invokeAll
ഉപയോഗിക്കുന്നത് പൊതുവെ നല്ലതാണ്. ഇത് പലപ്പോഴും മികച്ച ത്രെഡ് വിനിയോഗത്തിനും ലോഡ് ബാലൻസിംഗിനും കാരണമാകുന്നു.
4. എക്സെപ്ഷനുകൾ കൈകാര്യം ചെയ്യൽ
ഒരു compute()
മെത്തേഡിനുള്ളിൽ ഉണ്ടാകുന്ന എക്സെപ്ഷനുകൾ, നിങ്ങൾ join()
അല്ലെങ്കിൽ invoke()
ചെയ്യുമ്പോൾ ഒരു RuntimeException
(പലപ്പോഴും ഒരു CompletionException
)-ൽ പൊതിയപ്പെടുന്നു. നിങ്ങൾ ഈ എക്സെപ്ഷനുകൾ അൺറാപ്പ് ചെയ്ത് ഉചിതമായി കൈകാര്യം ചെയ്യേണ്ടിവരും.
try {
Long result = pool.invoke(task);
} catch (CompletionException e) {
// ടാസ്ക് ഉണ്ടാക്കിയ എക്സെപ്ഷൻ കൈകാര്യം ചെയ്യുക
Throwable cause = e.getCause();
if (cause instanceof IllegalArgumentException) {
// നിർദ്ദിഷ്ട എക്സെപ്ഷനുകൾ കൈകാര്യം ചെയ്യുക
} else {
// മറ്റ് എക്സെപ്ഷനുകൾ കൈകാര്യം ചെയ്യുക
}
}
5. കോമൺ പൂളിനെക്കുറിച്ച് മനസ്സിലാക്കുക
മിക്ക ആപ്ലിക്കേഷനുകൾക്കും, ForkJoinPool.commonPool()
ഉപയോഗിക്കുന്നത് ശുപാർശ ചെയ്യുന്ന സമീപനമാണ്. ഇത് ഒന്നിലധികം പൂളുകൾ കൈകാര്യം ചെയ്യുന്നതിനുള്ള ഓവർഹെഡ് ഒഴിവാക്കുകയും നിങ്ങളുടെ ആപ്ലിക്കേഷന്റെ വിവിധ ഭാഗങ്ങളിൽ നിന്നുള്ള ടാസ്ക്കുകളെ ഒരേ ത്രെഡ് പൂൾ പങ്കിടാൻ അനുവദിക്കുകയും ചെയ്യുന്നു. എന്നിരുന്നാലും, നിങ്ങളുടെ ആപ്ലിക്കേഷന്റെ മറ്റ് ഭാഗങ്ങളും കോമൺ പൂൾ ഉപയോഗിക്കുന്നുണ്ടാകാം എന്ന കാര്യം ശ്രദ്ധിക്കുക, ഇത് ശ്രദ്ധാപൂർവ്വം കൈകാര്യം ചെയ്തില്ലെങ്കിൽ തർക്കങ്ങൾക്ക് (contention) കാരണമായേക്കാം.
6. എപ്പോഴാണ് ഫോർക്ക്-ജോയിൻ ഉപയോഗിക്കേണ്ടാത്തത്
ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക്, ചെറിയ, റിക്കേഴ്സീവ് ഭാഗങ്ങളായി ഫലപ്രദമായി വിഭജിക്കാൻ കഴിയുന്ന കമ്പ്യൂട്ട്-ബൗണ്ട് ടാസ്ക്കുകൾക്കായി ഒപ്റ്റിമൈസ് ചെയ്തിരിക്കുന്നു. ഇത് സാധാരണയായി താഴെ പറയുന്നവയ്ക്ക് അനുയോജ്യമല്ല:
- I/O-ബൗണ്ട് ടാസ്ക്കുകൾ: നെറ്റ്വർക്ക് കോളുകൾ അല്ലെങ്കിൽ ഡിസ്ക് റീഡ്/റൈറ്റുകൾ പോലുള്ള ബാഹ്യ ഉറവിടങ്ങൾക്കായി കാത്തിരിക്കുന്നതിൽ കൂടുതൽ സമയം ചെലവഴിക്കുന്ന ടാസ്ക്കുകൾ, അസിങ്ക്രണസ് പ്രോഗ്രാമിംഗ് മോഡലുകളോ അല്ലെങ്കിൽ കമ്പ്യൂട്ടേഷന് ആവശ്യമായ വർക്കർ ത്രെഡുകളെ തടഞ്ഞുനിർത്താതെ ബ്ലോക്കിംഗ് പ്രവർത്തനങ്ങൾ കൈകാര്യം ചെയ്യുന്ന പരമ്പരാഗത ത്രെഡ് പൂളുകളോ ഉപയോഗിച്ച് കൈകാര്യം ചെയ്യുന്നതാണ് നല്ലത്.
- സങ്കീർണ്ണമായ ഡിപൻഡൻസികളുള്ള ടാസ്ക്കുകൾ: സബ്ടാസ്ക്കുകൾക്ക് സങ്കീർണ്ണമായ, നോൺ-റിക്കേഴ്സീവ് ഡിപൻഡൻസികൾ ഉണ്ടെങ്കിൽ, മറ്റ് കൺകറൻസി പാറ്റേണുകൾ കൂടുതൽ ഉചിതമായേക്കാം.
- വളരെ ചെറിയ ടാസ്ക്കുകൾ: വളരെ ചെറിയ പ്രവർത്തനങ്ങൾക്ക്, ടാസ്ക്കുകൾ ഉണ്ടാക്കുന്നതിനും കൈകാര്യം ചെയ്യുന്നതിനും ഉള്ള ഓവർഹെഡ് പ്രയോജനങ്ങളെക്കാൾ കൂടുതലായിരിക്കും.
ആഗോളതലത്തിലുള്ള പരിഗണനകളും ഉപയോഗസാധ്യതകളും
മൾട്ടി-കോർ പ്രോസസ്സറുകൾ കാര്യക്ഷമമായി ഉപയോഗിക്കാനുള്ള ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്കിന്റെ കഴിവ്, പലപ്പോഴും താഴെ പറയുന്നവ കൈകാര്യം ചെയ്യുന്ന ആഗോള ആപ്ലിക്കേഷനുകൾക്ക് അമൂല്യമാണ്:
- വലിയ തോതിലുള്ള ഡാറ്റാ പ്രോസസ്സിംഗ്: ഭൂഖണ്ഡങ്ങളിലുടനീളം ഡെലിവറി റൂട്ടുകൾ ഒപ്റ്റിമൈസ് ചെയ്യേണ്ട ഒരു ആഗോള ലോജിസ്റ്റിക്സ് കമ്പനി സങ്കൽപ്പിക്കുക. റൂട്ട് ഒപ്റ്റിമൈസേഷൻ അൽഗോരിതങ്ങളിൽ ഉൾപ്പെട്ടിരിക്കുന്ന സങ്കീർണ്ണമായ കണക്കുകൂട്ടലുകൾ സമാന്തരമാക്കാൻ ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക് ഉപയോഗിക്കാം.
- തത്സമയ അനലിറ്റിക്സ്: ഒരു ധനകാര്യ സ്ഥാപനം വിവിധ ആഗോള എക്സ്ചേഞ്ചുകളിൽ നിന്നുള്ള മാർക്കറ്റ് ഡാറ്റ ഒരേസമയം പ്രോസസ്സ് ചെയ്യാനും വിശകലനം ചെയ്യാനും ഇത് ഉപയോഗിച്ചേക്കാം, ഇത് തത്സമയ ഉൾക്കാഴ്ചകൾ നൽകുന്നു.
- ഇമേജ്, മീഡിയ പ്രോസസ്സിംഗ്: ലോകമെമ്പാടുമുള്ള ഉപയോക്താക്കൾക്കായി ഇമേജ് റീസൈസിംഗ്, ഫിൽട്ടറിംഗ് അല്ലെങ്കിൽ വീഡിയോ ട്രാൻസ്കോഡിംഗ് വാഗ്ദാനം ചെയ്യുന്ന സേവനങ്ങൾക്ക് ഈ പ്രവർത്തനങ്ങൾ വേഗത്തിലാക്കാൻ ഫ്രെയിംവർക്ക് പ്രയോജനപ്പെടുത്താം. ഉദാഹരണത്തിന്, ഒരു കണ്ടന്റ് ഡെലിവറി നെറ്റ്വർക്ക് (CDN) ഉപയോക്താവിന്റെ സ്ഥാനവും ഉപകരണവും അടിസ്ഥാനമാക്കി വ്യത്യസ്ത ഇമേജ് ഫോർമാറ്റുകളോ റെസല്യൂഷനുകളോ കാര്യക്ഷമമായി തയ്യാറാക്കാൻ ഇത് ഉപയോഗിച്ചേക്കാം.
- ശാസ്ത്രീയ സിമുലേഷനുകൾ: സങ്കീർണ്ണമായ സിമുലേഷനുകളിൽ (ഉദാ. കാലാവസ്ഥാ പ്രവചനം, മോളിക്യുലാർ ഡൈനാമിക്സ്) പ്രവർത്തിക്കുന്ന ലോകത്തിന്റെ വിവിധ ഭാഗങ്ങളിലുള്ള ഗവേഷകർക്ക്, കനത്ത കമ്പ്യൂട്ടേഷണൽ ലോഡ് സമാന്തരമാക്കാനുള്ള ഫ്രെയിംവർക്കിന്റെ കഴിവിൽ നിന്ന് പ്രയോജനം നേടാനാകും.
ഒരു ആഗോള പ്രേക്ഷകർക്കായി വികസിപ്പിക്കുമ്പോൾ, പ്രകടനവും പ്രതികരണശേഷിയും നിർണ്ണായകമാണ്. നിങ്ങളുടെ ജാവ ആപ്ലിക്കേഷനുകൾക്ക് ഫലപ്രദമായി സ്കെയിൽ ചെയ്യാനും നിങ്ങളുടെ ഉപയോക്താക്കളുടെ ഭൂമിശാസ്ത്രപരമായ വിതരണമോ നിങ്ങളുടെ സിസ്റ്റങ്ങളിൽ വരുന്ന കമ്പ്യൂട്ടേഷണൽ ആവശ്യകതകളോ പരിഗണിക്കാതെ ഒരു തടസ്സമില്ലാത്ത അനുഭവം നൽകാനും കഴിയുമെന്ന് ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക് ഉറപ്പുള്ള ഒരു സംവിധാനം നൽകുന്നു.
ഉപസംഹാരം
കമ്പ്യൂട്ടേഷണൽ-ഇന്റെൻസീവ് ടാസ്ക്കുകൾ സമാന്തരമായി കൈകാര്യം ചെയ്യുന്നതിന് ആധുനിക ജാവ ഡെവലപ്പർമാരുടെ ആയുധപ്പുരയിലെ ഒഴിച്ചുകൂടാനാവാത്ത ഒരു ഉപകരണമാണ് ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക്. വിഭജിച്ച് കീഴടക്കുക എന്ന തന്ത്രം സ്വീകരിക്കുന്നതിലൂടെയും ForkJoinPool
-നുള്ളിലെ വർക്ക്-സ്റ്റീലിംഗിന്റെ ശക്തി പ്രയോജനപ്പെടുത്തുന്നതിലൂടെയും, നിങ്ങളുടെ ആപ്ലിക്കേഷനുകളുടെ പ്രകടനവും സ്കേലബിലിറ്റിയും ഗണ്യമായി വർദ്ധിപ്പിക്കാൻ കഴിയും. RecursiveTask
, RecursiveAction
എന്നിവ എങ്ങനെ ശരിയായി നിർവചിക്കാമെന്നും, ഉചിതമായ ത്രെഷോൾഡുകൾ തിരഞ്ഞെടുക്കാമെന്നും, ടാസ്ക് ഡിപൻഡൻസികൾ കൈകാര്യം ചെയ്യാമെന്നും മനസ്സിലാക്കുന്നത് മൾട്ടി-കോർ പ്രോസസ്സറുകളുടെ പൂർണ്ണമായ കഴിവുകൾ പ്രയോജനപ്പെടുത്താൻ നിങ്ങളെ അനുവദിക്കും. ആഗോള ആപ്ലിക്കേഷനുകളുടെ സങ്കീർണ്ണതയും ഡാറ്റാ അളവും വർദ്ധിച്ചുകൊണ്ടിരിക്കുമ്പോൾ, ലോകമെമ്പാടുമുള്ള ഉപയോക്താക്കൾക്ക് സേവനം നൽകുന്ന കാര്യക്ഷമവും വേഗതയേറിയതും ഉയർന്ന പ്രകടനവുമുള്ള സോഫ്റ്റ്വെയർ സൊല്യൂഷനുകൾ നിർമ്മിക്കുന്നതിന് ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്കിൽ വൈദഗ്ദ്ധ്യം നേടുന്നത് അത്യാവശ്യമാണ്.
നിങ്ങളുടെ ആപ്ലിക്കേഷനിൽ റിക്കേഴ്സീവ് ആയി വിഭജിക്കാൻ കഴിയുന്ന കമ്പ്യൂട്ട്-ബൗണ്ട് ടാസ്ക്കുകൾ തിരിച്ചറിഞ്ഞുകൊണ്ട് ആരംഭിക്കുക. ഫ്രെയിംവർക്ക് ഉപയോഗിച്ച് പരീക്ഷണം നടത്തുക, പ്രകടന നേട്ടങ്ങൾ അളക്കുക, ഒപ്റ്റിമൽ ഫലങ്ങൾ നേടുന്നതിന് നിങ്ങളുടെ ഇംപ്ലിമെന്റേഷനുകൾ മെച്ചപ്പെടുത്തുക. കാര്യക്ഷമമായ സമാന്തര എക്സിക്യൂഷനിലേക്കുള്ള യാത്ര തുടർന്നുകൊണ്ടിരിക്കും, ആ പാതയിലെ ഒരു വിശ്വസ്ത കൂട്ടാളിയാണ് ഫോർക്ക്-ജോയിൻ ഫ്രെയിംവർക്ക്.