Object hydration എന്നത് നിങ്ങൾ അത് അളന്നു നോക്കുന്നത് വരെ പരിഹരിക്കപ്പെട്ട ഒരു പ്രശ്നമാണെന്ന് തോന്നും. നിങ്ങൾ ഡാറ്റാബേസിൽ നിന്ന് ഒരു റോ (row) എടുക്കുന്നു, ആ അറേയെ ഒരു ഒബ്‌ജക്റ്റിലേക്ക് മാപ്പ് ചെയ്യുന്നു, എന്നിട്ട് മുന്നോട്ട് പോകുന്നു. നമ്മളിൽ ഭൂരിഭാഗവും ഇതിനെ പ്ലംബിംഗ് പോലെയാണ് കാണുന്നത്—കാണാൻ കഴിയാത്തതും, ആകർഷകമല്ലാത്തതും, വേഗത്തിൽ നടക്കുമെന്ന് കരുതിയിരിക്കുന്നതുമായ ഒന്ന്. എന്നാൽ ഒരു ദിവസം നിങ്ങൾ ഒരു ബാച്ച് ജോബ് അല്ലെങ്കിൽ ഒരു ക്യൂ വർക്കർ പ്രൊഫൈൽ ചെയ്യുമ്പോൾ, സിപിയു (CPU) സമയത്തിന്റെ വലിയൊരു ഭാഗം മാപ്പറിൽ (mapper) നഷ്ടപ്പെടുന്നുണ്ടെന്ന് ശ്രദ്ധിക്കും. ആ തിരിച്ചറിവാണ് HydraType-ലേക്ക് നയിച്ചത്; ഒരു കഠിനമായ നിയമത്തിന് ചുറ്റിയാണ് ഈ ഹൈഡ്രേറ്റർ നിർമ്മിച്ചിരിക്കുന്നത്: ഒരു ക്ലാസ് അതിന്റെ പ്രോപ്പർട്ടികൾ ഉപയോഗിക്കാത്ത ഫീച്ചറുകൾക്കായി ഒരിക്കലും വില നൽകരുത്.

ഒരു ഫീൽഡ് മാറ്റങ്ങൾ ആവശ്യമില്ലാത്ത ഒരു സാധാരണ സ്ട്രിംഗ് (string) ആണെങ്കിൽ, ജനറേറ്റഡ് കോഡ് ഒരു മനുഷ്യൻ കൈകൊണ്ട് എഴുതിയത് പോലെ ലളിതമായിരിക്കണം. നേരിട്ടുള്ള അസൈൻമെന്റ് (Direct assignment). ലൂപ്പുകളോ, പൈപ്പ്‌ലൈനുകളോ, റിഫ്ലക്ഷനോ (reflection) ആവശ്യമില്ല.

എന്താണ് ഹൈഡ്രേഷൻ യഥാർത്ഥത്തിൽ ചെലവാക്കുന്നത്?

ഒറ്റനോട്ടത്തിൽ, ['name' => 'Alice', 'age' => 30] എന്നതിനെ ഒരു User ഒബ്‌ജക്റ്റാക്കി മാറ്റുന്നത് വളരെ ലളിതമായി തോന്നും. എന്നാൽ നിങ്ങൾക്ക് ഫ്ലെക്സിബിലിറ്റി (flexibility) ആവശ്യമായി വരുമ്പോഴാണ് പ്രശ്നം തുടങ്ങുന്നത്. മിക്ക ജനറൽ പർപ്പസ് ഹൈഡ്രേറ്ററുകളും റൺടൈമിൽ ക്ലാസുകളെ പരിശോധിക്കാൻ reflection ഉപയോഗിക്കുന്നു. അവ മെറ്റാഡാറ്റ മാപ്പുകൾ നിർമ്മിക്കുകയും, ടൈപ്പ് കോർഷൻ (type coercion) നടത്തുകയും, നെസ്റ്റഡ് ഒബ്‌ജക്റ്റ് ഗ്രാഫുകൾ (nested object graphs) പരിഹരിക്കുകയും ചെയ്യുന്നു. അവ പൈപ്പ്‌ലൈനുകളെയും ഇഷ്ടപ്പെടുന്നു. ആ പൈപ്പ്‌ലൈൻ ശൂന്യമാണെങ്കിൽ പോലും, ഓരോ മൂല്യവും മ്യൂട്ടേറ്ററുകൾ (mutators), അസർഷനുകൾ (assertions), ട്രാൻസ്ഫോർമറുകൾ (transformers) എന്നിവയിലൂടെ കടന്നുപോകുന്നു. ഈ ലൂപ്പ് ഘടന തന്നെ അധികഭാരം (overhead) ഉണ്ടാക്കുന്നു.

കുറച്ച് റെക്കോർഡുകൾ മാത്രം എടുക്കുമ്പോൾ നിങ്ങൾ ഇത് ശ്രദ്ധിക്കില്ലായിരിക്കാം. എന്നാൽ ആധുനിക PHP ആപ്ലിക്കേഷനുകൾ പതിവായി ആയിരക്കണക്കിന് ക്യൂ മെസ്സേജുകൾ പ്രോസസ്സ് ചെയ്യുകയോ, വലിയ CSV സെറ്റുകൾ ഇംപോർട്ട് ചെയ്യുകയോ, അല്ലെങ്കിൽ Elasticsearch-ൽ നിന്ന് വലിയ റിസൾട്ട് സെറ്റുകൾ ഹൈഡ്രേറ്റ് ചെയ്യുകയോ ചെയ്യുന്നു. അത്തരം സാഹചര്യങ്ങളിൽ, ഓരോ ഫീൽഡിലും ഏതാനും മൈക്രോസെക്കൻഡുകൾ പോലും ചെലവാക്കുന്ന ഒരു മാപ്പർ വലിയൊരു തടസ്സമായി (bottleneck) മാറും. എട്ട് ഫീൽഡുകൾ വീതമുള്ള ആയിരം ഒബ്‌ജക്റ്റുകൾ വരുമ്പോൾ, അത് നിങ്ങൾക്ക് അനുഭവവേദ്യമാകുന്ന ഒരു അധികഭാരമായി മാറുന്നു.

സീറോ-ഓവർഹെഡ് ഫിലോസഫി (The Zero-Overhead Philosophy)

HydraType-ന്റെ പിന്നിലെ പ്രധാന ആശയം ഫീച്ചർ ഐസൊലേഷൻ (feature isolation) ആണ്. ടൈപ്പ് കൺവേർഷൻ, നെസ്റ്റഡ് ഒബ്‌ജക്റ്റ് ഹൈഡ്രേഷൻ, അസർഷനുകൾ എന്നിവയെല്ലാം ഇതിൽ പിന്തുണയ്ക്കുന്നുണ്ടെങ്കിലും അവയിൽ ഒന്നും നിർബന്ധമല്ല. ഒരു പ്രോപ്പർട്ടിക്ക് അറേയിൽ നിന്ന് ഒബ്‌ജക്റ്റിലേക്ക് നേരിട്ടുള്ള ഒരു കോപ്പി മാത്രമാണ് ആവശ്യമെങ്കിൽ, ജനറേറ്റഡ് റൈറ്ററിൽ അത് മാത്രമായിരിക്കും ഉണ്ടാവുക. ഒരു സാധാരണ സ്കെയിലർ ഫീൽഡിനെ (scalar field) ആവശ്യമില്ലാത്ത വാലിഡേറ്ററുകൾക്ക് പിന്നിൽ വരിനിൽക്കാൻ നിർബന്ധിക്കുന്ന ഒരു പങ്കിട്ട പൈപ്പ്‌ലൈൻ അവിടെയില്ല.

ഇത് കേൾക്കുന്നതിനേക്കാൾ പ്രയാസകരമാണ് നേടിയെടുക്കാൻ. പല ലൈബ്രറികളും കോഡ്ബേസ് ചെറുതായും ഏകതാനമായും നിലനിർത്താൻ എല്ലാ ഫീൽഡുകൾക്കും ഒരൊറ്റ എക്സിക്യൂഷൻ പാത്ത് ഉപയോഗിക്കുന്നു. HydraType ഇതിന് വിപരീതമാണ് ചെയ്യുന്നത്. ഓരോ ടാർഗെറ്റ് DTO-യ്ക്കും വേണ്ടി അത് ഒരു പ്രത്യേക PHP ക്ലാസ് നിർമ്മിക്കുന്നു, ആ ക്ലാസിന് കൃത്യമായി ആവശ്യമുള്ള ഓപ്പറേഷനുകൾ മാത്രം അത് നൽകുന്നു. സങ്കീർണ്ണത എന്നത് ആവശ്യമുള്ളപ്പോൾ മാത്രം തിരഞ്ഞെടുക്കാവുന്ന ഒന്നായി മാറുന്നു (opt-in), അല്ലാതെ എല്ലാ പ്രോപ്പർട്ടികൾക്കും മേൽ ചുമത്തുന്ന ഒരു പൊതുവായ നികുതി പോലെയല്ല.

എങ്ങനെയാണ് ഈ വേഗത ലഭിക്കുന്നത്?

ജനറിക് റിഫ്ലക്ഷൻ അധിഷ്ഠിത ടൂളുകളിൽ നിന്നും മറ്റ് ജനറേറ്റഡ് സമീപനങ്ങളിൽ നിന്നും HydraType-നെ വേർതിരിക്കുന്ന നാല് പ്രധാന കാര്യങ്ങൾ ഇവയാണ്.

1. കോഡ് ഒരിക്കൽ മാത്രം നിർമ്മിക്കുക, എപ്പോഴും പ്രവർത്തിപ്പിക്കുക

HydraType നിങ്ങളുടെ ടാർഗെറ്റ് ക്ലാസിനെ ഒരു തവണ മാത്രം പരിശോധിക്കുന്നു, തുടർന്ന് അതിന്റെ കൃത്യമായ രൂപത്തിന് അനുയോജ്യമായ ഒരു പ്രത്യേക PHP ക്ലാസ് എഴുതുന്നു. നിങ്ങളുടെ DTO-യിൽ ഒരു പബ്ലിക് സ്ട്രിംഗ് $name, ഒരു int $status, കൂടാതെ ഒരു പ്രൈവറ്റ് DateTime $createdAt എന്നിവ ഉണ്ടെങ്കിൽ, ജനറേറ്റഡ് റൈറ്ററിന് ഇതെല്ലാം മുൻകൂട്ടി അറിയാം. റൺടൈമിൽ ആവർത്തിച്ചുള്ള റിഫ്ലക്ഷനോ, സ്ട്രിംഗ് പാഴ്സിംഗോ, മെറ്റാഡാറ്റ നിർമ്മാണമോ നടക്കുന്നില്ല. ഒരിക്കൽ OPCache ജനറേറ്റഡ് ഫയൽ കംപൈൽ ചെയ്താൽ, ഈ ഹൈഡ്രേറ്റർ കൈകൊണ്ട് എഴുതിയ PHP കോഡിന് തുല്യമായിരിക്കും.

2. ശൂന്യമായ പൈപ്പ്‌ലൈനുകൾ ഒഴിവാക്കുക

മിക്ക ഹൈഡ്രേറ്ററുകളും റൺടൈം പൈപ്പ്‌ലൈനിനെ അടിസ്ഥാനമാക്കിയാണ് പ്രവർത്തിക്കുന്നത്. അവ മ്യൂട്ടേറ്ററുകൾ, വാലിഡേറ്ററുകൾ, ട്രാൻസ്ഫോർമറുകൾ എന്നിവയുടെ ഒരു അറേ സൂക്ഷിക്കുകയും ഓരോ മൂല്യത്തിലൂടെയും അവ ഇറ്ററേറ്റ് (iterate) ചെയ്യുകയും ചെയ്യുന്നു. ആ അറേകൾ ശൂന്യമാണെങ്കിൽ പോലും, foreach അല്ലെങ്കിൽ array_reduce പ്രവർത്തിച്ചുകൊണ്ടേയിരിക്കും. HydraType ഇത് പൂർണ്ണമായും ഒഴിവാക്കുന്നു. കോഡ് ജനറേഷൻ സമയത്ത് തന്നെ ഒരു പ്രോപ്പർട്ടിക്ക് യഥാർത്ഥത്തിൽ എന്താണ് ആവശ്യമെന്ന് അത് പരിശോധിക്കുന്നു. ഓപ്പറേഷൻ ലിസ്റ്റ് ശൂന്യമാണെങ്കിൽ, അത് $object->name = $data['name']; എന്നതുപോലെയുള്ള ഒരു നേരിട്ടുള്ള അസൈൻമെന്റ് നൽകുന്നു. റൺടൈമിൽ ഒന്നും ഇറ്ററേറ്റ് ചെയ്യേണ്ടതില്ല, കാരണം ജനറേറ്റർ നേരത്തെ തന്നെ ആ കാര്യങ്ങൾ ചെയ്തു കഴിഞ്ഞു.

3. Reflection ഒഴിവാക്കാൻ Closure Scoping ഉപയോഗിക്കുക

ബാഹ്യ മാപ്പറുകളെ സംബന്ധിച്ചിടത്തോളം പ്രൈവറ്റ് പ്രോപ്പർട്ടികൾ (private properties) എപ്പോഴും ഒരു തലവേദനയാണ്. സാധാരണയായി ഇതിനായി ReflectionProperty::setValue() ഉപയോഗിക്കാറുണ്ട്, എന്നാൽ നേരിട്ടുള്ള പ്രോപ്പർട്ടി ആക്സസിനെ അപേക്ഷിച്ച് ആ മെത്തേഡ് വലിയൊരു സമയനഷ്ടം ഉണ്ടാക്കുന്നു. HydraType Closure::bind ഉപയോഗിച്ച് ഇത് മറികടക്കുന്നു. ജനറേറ്റഡ് റൈറ്ററിൽ ടാർഗെറ്റ് ക്ലാസിന് പരിമിതപ്പെടുത്തിയ (scoped) ക്ലോഷറുകൾ (closures) അടങ്ങിയിരിക്കുന്നു, ഇത് റിഫ്ലക്ഷൻ ഇല്ലാതെ തന്നെ പ്രൈവറ്റ്, പ്രൊട്ടക്റ്റഡ് പ്രോപ്പർട്ടികളിലേക്ക് നേരിട്ട് പ്രവേശിക്കാൻ അവയെ അനുവദിക്കുന്നു. കൺസ്ട്രക്ഷൻ സമയത്ത് ഒരിക്കൽ മാത്രം ഈ ബൈൻഡിംഗ് നടക്കുന്നു; അതിനുശേഷം, ക്ലോഷർ ഏതാണ്ട് നാറ്റീവ് വേഗതയിൽ പ്രവർത്തിക്കുന്നു.

4. Writer വീണ്ടും ഉപയോഗിക്കുക

ചില ലൈബ്രറികൾ ഹൈഡ്രേറ്റ് ചെയ്യുന്ന ഓരോ ഒബ്‌ജക്റ്റിനും വേണ്ടി പുതിയൊരു സ്ട്രാറ്റജിയോ റിഫ്ലക്ഷൻ ഗ്രാഫോ ഇൻസ്റ്റാന്ഷ്യേറ്റ് ചെയ്യുന്നു. HydraType അതിന്റെ റൈറ്റർ ഒരിക്കൽ മാത്രം നിർമ്മിക്കുന്നു, തുടർന്ന് ആയിരക്കണക്കിന് ഒബ്‌ജക്റ്റുകൾക്കായി ആ ഇൻസ്റ്റൻസ് തന്നെ വീണ്ടും ഉപയോഗിക്കുന്നു. ഓരോ ഒബ്‌ജക്റ്റിനും ഉണ്ടാകുന്ന ചെലവ് പ്രോപ്പർട്ടി മൂല്യങ്ങൾ സെറ്റ് ചെയ്യുന്നതിനും റിസൾട്ട് തിരികെ നൽകുന്നതിനും ആവശ്യമായ ഏറ്റവും കുറഞ്ഞ അളവിലേക്ക് ചുരുങ്ങുന്നു.

കണക്കുകൾ (The Numbers)

PHP 8.2-ൽ, വ്യത്യാസം വളരെ വ്യക്തമാണ്:

  • HydraType: 267.9 ns
  • Ocramius GeneratedHydrator: 307.9 ns
  • Symfony PropertyNormalizer: 8,499.0 ns
  • Valinor: 10,755.2 ns

Symfony PropertyNormalizer-ഉം Valinor-ഉം ശക്തമായ ടൂളുകളാണ്, എന്നാൽ അവ പൊതുവായ ആവശ്യങ്ങൾക്കായി ഉപയോഗിക്കുന്നവയാണ് (generalists). ഫോർമാറ്റ് ഡിറ്റക്ഷൻ, നോർമലൈസറുകൾ, ഡീപ്പ് ഒബ്‌ജക്റ്റ് ഗ്രാഫുകൾ എന്നിവ കൈകാര്യം ചെയ്യുന്ന ഒരു സീരിയലൈസർ കംപോണന്റിന്റെ ഭാഗമാണ് PropertyNormalizer. ടൈപ്പ് ട്രീസുകളിലും (type trees) കോർഷനിലും (coercion) Valinor വളരെ കർശനമായ നിലപാടുള്ളതാണ്. ആ കരുത്തിന് ഒരു വിലയുണ്ട്. ഈ പരീക്ഷണത്തിൽ, അവ HydraType-നേക്കാൾ ഏകദേശം മുപ്പത് മുതൽ നാൽപ്പത് മടങ്ങ് വരെ സാവധാനത്തിലായിരുന്നു. കോഡ് ജനറേഷൻ ഉപയോഗിക്കുന്ന Ocramius GeneratedHydrator പോലും പൈപ്പ്‌ലൈനുകളിലും പ്രോപ്പർട്ടി ആക്സസ്സിലുമുള്ള ആർക്കിടെക്ചറൽ വ്യത്യാസങ്ങൾ കാരണം അല്പം പിന്നിലാണ്.

സന്ദർഭം പ്രധാനമാണ്. ഒരു ഡാറ്റാബേസ് ക്വറിയോ അല്ലെങ്കിൽ ഒരു HTTP റൗണ്ട്‌ട്രിപ്പോ ഈ സംഖ്യകളെല്ലാം നിസ്സാരമാക്കുന്നു. ഒരു ക്വറിക്ക് ശേഷം മൂന്ന് ഒബ്‌ജക്റ്റുകൾ മാത്രം ഹൈഡ്രേറ്റ് ചെയ്യുന്നതാണെങ്കിൽ, നാനോസെക്കൻഡുകൾക്കായി സമയം കളയുന്നത് ഊർജ്ജ অপചയമാണ്. നിങ്ങൾ സ്കെയിൽ ചെയ്യുമ്പോൾ ഈ വ്യത്യാസം പ്രസക്തമാകുന്നു. അമ്പതിനായിരം റെക്കോർഡുകൾ ഹൈഡ്രേറ്റ് ചെയ്യുന്ന ഒരു ബാച്ച് പ്രോസസ്സോ, അല്ലെങ്കിൽ കാഷെ അറേകളിൽ നിന്ന് ഒബ്‌ജക്റ്റുകൾ പുനർനിർമ്മിക്കുന്ന ഒരു ക്യൂ കൺസ്യൂമറോ ആണെങ്കിൽ, 267 നാനോസെക്കൻഡും പത്ത് മൈക്രോസെക്കൻഡും തമ്മിലുള്ള വ്യത്യാസം അനുഭവപ്പെടും. ഫീൽഡുകളിലും റോകളിലും ഇത് വർദ്ധിക്കുമ്പോൾ, ബിസിനസ് ലോജിക് മാറ്റാതെ തന്നെ വേഗതയേറിയ മാപ്പർ ഉപയോഗിച്ച് ജോലികൾ കുറച്ച് സെക്കൻഡുകൾ ലാഭിക്കാൻ സാധിക്കും.

യഥാർത്ഥ പാഠം

എല്ലാ പ്രോജക്റ്റുകൾക്കും ഒരു കസ്റ്റം ഹൈഡ്രേറ്റർ ആവശ്യമാണെന്നല്ല ഇതിന്റെ പാഠം. ആർക്കിടെക്ചർ തിരഞ്ഞെടുപ്പുകൾ അതിന്റെ ഫലം വർദ്ധിപ്പിക്കുന്നു എന്നതാണ് കാര്യം. നിങ്ങൾ ആവശ്യപ്പെടുന്ന കാര്യങ്ങൾ മാത്രം ഉൾക്കൊള്ളുന്ന കോഡ് ജനറേറ്റ് ചെയ്യുന്നതിലൂടെ, HydraType സാധാരണ പ്രോപ്പർട്ടികൾ വേഗതയേറിയതാക്കി നിലനിർത്തുന്നു, അതേസമയം സങ്കീർണ്ണമായവയ്ക്കായി ഒരു എസ്‌കേപ്പ് ഹാച്ച് (escape hatch) കൂടി നൽകുന്നു. ടൈപ്പ് കൺവേർഷനും നെസ്റ്റഡ് ഒബ്‌ജക്റ്റുകളും ആവശ്യമുള്ളപ്പോൾ ലഭ്യമാണ്, എന്നാൽ ഒബ്‌ജക്റ്റിലെ ഓരോ സ്കെലാർ സ്ട്രിംഗിന്റെയും പെർഫോമൻസ് കുറയ്ക്കാൻ അവ കാരണമാകില്ല.

ടൂളുകൾ മാറ്റുന്നതിന് മുമ്പ് പ്രൊഫൈലിംഗ് നടത്തുക. നിങ്ങളുടെ യഥാർത്ഥ ഉപയോഗത്തിൽ ഹൈഡ്രേഷൻ ഒരു പ്രശ്നമായി കാണുന്നില്ലെങ്കിൽ, പരിഹരിക്കാൻ ഒന്നുമില്ല. എന്നാൽ വലിയ ഡാറ്റാസെറ്റുകൾ ജനറിക് മാപ്പറുകളിലൂടെ കടത്തിവിടുമ്പോൾ സിപിയു (CPU) ഉപയോഗം കൂടുന്നത് ശ്രദ്ധിക്കുന്നുണ്ടെങ്കിൽ, സാധാരണ PHP അസൈൻമെന്റുകൾ ഇപ്പോഴും ലഭ്യമാണെന്ന് ഓർക്കുക. ചിലപ്പോൾ ഏറ്റവും വേഗതയേറിയ കോഡ് എന്നത് നിങ്ങൾ തന്നെ എഴുതിയിരുന്ന, ഒരിക്കൽ ജനറേറ്റ് ചെയ്ത് പിന്നീട് മറന്നുപോയ കോഡ് മാത്രമായിരിക്കും.