ஆப்ஜெக்ட் ஹைட்ரேஷன் (Object hydration) என்பது அளவிடும் வரை ஒரு தீர்க்கப்பட்ட பிரச்சனை போலத் தோன்றும். நீங்கள் தரவுத்தளத்திலிருந்து (database) ஒரு வரிசையைத் (row) திரும்பப் பெறுகிறீர்கள், ஒரு அரேவை (array) ஆப்ஜெக்ட்டாக மாற்றுகிறீர்கள், பிறகு அடுத்த வேலைக்குச் செல்கிறீர்கள். நம்மில் பெரும்பாலோர் இதை ஒரு குழாய் அமைப்பு (plumbing) போலக் கருதுகிறோம்—கண்ணுக்குத் தெரியாத, சலிப்பூட்டும், ஆனால் போதுமான வேகத்தில் இருக்கும் என்று கருதப்படும் ஒன்று. பிறகு ஒரு நாள் நீங்கள் ஒரு பேட்ச் ஜாப் (batch job) அல்லது கியூ வொர்க்கரை (queue worker) ஆய்வு செய்யும் போது, CPU நேரத்தின் ஒரு பெரிய பகுதி மேப்பரில் (mapper) வீணாவதை கவனிக்கிறீர்கள். அந்த உணர்தலே HydraType உருவாகக் காரணமாக அமைந்தது. இது ஒரு பிடிவாதமான விதியைப் பின்பற்றி உருவாக்கப்பட்டது: ஒரு கிளாஸின் (class) பண்புகள் (properties) பயன்படுத்தாத அம்சங்களுக்காக அந்த கிளாஸ் எதையும் விலை கொடுக்கக் கூடாது.

ஒரு ஃபீல்டு (field) எந்த மாற்றமும் தேவையில்லாத ஒரு சாதாரண ஸ்ட்ரிங் (string) என்றால், உருவாக்கப்பட்ட குறியீடு (code) ஒரு மனிதன் கையால் எழுதியது போலவே இருக்க வேண்டும். நேரடி ஒதுக்கீடு (Direct assignment). லூப்கள் (loops), பைப்லைன்கள் (pipelines) அல்லது ரிஃப்ளெக்ஷன் (reflection) எதுவுமே இருக்கக்கூடாது.

ஹைட்ரேஷன் உண்மையில் எவ்வளவு சுமையை ஏற்படுத்துகிறது

முதல் பார்வையில், ['name' => 'Alice', 'age' => 30] என்பதை ஒரு User ஆப்ஜெக்ட்டாக மாற்றுவது மிக எளிதாகத் தோன்றலாம். உங்களுக்கு நெகிழ்வுத்தன்மை (flexibility) தேவைப்படும்போது சிக்கல் தொடங்குகிறது. பெரும்பாலான பொதுவான ஹைட்ரேட்டர்கள் (hydrators), ரன்டைமில் (runtime) கிளாஸ்களை ஆய்வு செய்ய ரிஃப்ளெக்ஷனைச் சார்ந்துள்ளன. அவை மெட்டாடேட்டா மேப்புகளை (metadata maps) உருவாக்குகின்றன, டைப் கோயேர்ஷன் (type coercion) செய்கின்றன மற்றும் நெஸ்டட் ஆப்ஜெக்ட் கிராஃப்களை (nested object graphs) தீர்க்கின்றன. அவை பைப்லைன்களையும் அதிகம் பயன்படுத்துகின்றன. அந்த வரிசை காலியாக இருந்தாலும் கூட, ஒவ்வொரு மதிப்பும் மியூட்டேட்டர்கள் (mutators), அஸர்ஷன்கள் (assertions) மற்றும் டிரான்ஸ்ஃபார்மர்கள் (transformers) ஆகியவற்றின் வழியாகச் செல்கிறது. இந்த லூப் அமைப்பு (loop structure) கூடுதல் சுமையை (overhead) ஏற்படுத்துகிறது.

சில டஜன் பதிவுகளைப் பெறும்போது நீங்கள் இதைக் கவனிக்க மாட்டீர்கள். ஆனால் நவீன PHP அப்ளிகேஷன்கள் வழக்கமாக ஆயிரக்கணக்கான கியூ மெசேஜ்களை (queue messages) செயலாக்குகின்றன, மிகப்பெரிய CSV தொகுப்புகளை இறக்குமதி செய்கின்றன அல்லது Elasticsearch இலிருந்து ஆழமான ரிசல்ட் செட்களை (result sets) ஹைட்ரேட் செய்கின்றன. அத்தகைய சூழல்களில், ஒரு ஃபீல்டுக்கு சில மைக்ரோசெகண்டுகளைக் கூட செலவழிக்கும் மேப்பர் ஒரு உண்மையான பாட்டில்நெக் (bottleneck) ஆகிவிடும். எட்டு ஃபீல்டுகள் கொண்ட ஆயிரம் ஆப்ஜெக்ட்கள் என்பது நீங்கள் உணரக்கூடிய ஒரு பெரிய சுமையாக (tax) மாறும்.

சுமையற்ற தத்துவம் (The Zero-Overhead Philosophy)

HydraType-ன் மையக் கருத்து அம்சத் தனிமைப்படுத்தல் (feature isolation) ஆகும். டைப் கன்வெர்ஷன் (Type conversion), நெஸ்டட் ஆப்ஜெக்ட் ஹைட்ரேஷன் மற்றும் அஸர்ஷன்கள் ஆகியவை ஆதரிக்கப்படுகின்றன, ஆனால் அவை எதுவும் கட்டாயமானவை அல்ல. ஒரு பண்பிற்கு (property) அரேவிலிருந்து ஆப்ஜெக்ட்டிற்கு நேரடி நகல் மட்டுமே தேவைப்பட்டால், உருவாக்கப்பட்ட ரைட்டர் (writer) அதை மட்டுமே கொண்டிருக்கும். ஒரு சாதாரண ஸ்கேலார் ஃபீல்டு (scalar field), அதற்குத் தேவையில்லாத வேலிடேட்டர்களுக்காக (validators) வரிசையில் காத்திருக்க வேண்டிய கட்டாயம் இருக்காது.

இது சொல்வதை விடச் சாதிப்பது கடினம். பல லைப்ரரிகள் குறியீட்டுத் தொகுப்பை (codebase) சிறியதாகவும் சீராகவும் வைத்திருக்க ஒவ்வொரு ஃபீல்டுக்கும் ஒரே எக்ஸிகியூஷன் பாதையைப் (execution path) பயன்படுத்துகின்றன. HydraType இதற்கு நேர்மாறான அணுகுமுறையைக் கையாள்கிறது. இது ஒவ்வொரு இலக்கு DTO-விற்கும் ஒரு தனித்துவமான PHP கிளாஸை உருவாக்குகிறது, அந்த குறிப்பிட்ட கிளாஸிற்குத் தேவையான செயல்பாடுகளை மட்டுமே வழங்குகிறது. சிக்கல்தன்மை என்பது விருப்பத்தேர்வு (opt-in) மட்டுமே தவிர, ஒவ்வொரு பண்பிற்கும் விதிக்கப்படும் பொதுவான சுமை அல்ல.

வேகம் எவ்வாறு கிடைக்கிறது

நான்கு குறிப்பிட்ட தேர்வுகள் HydraType-ஐ பொதுவான ரிஃப்ளெக்ஷன் சார்ந்த கருவிகளிலிருந்தும், பிற உருவாக்கப்பட்ட அணுகுமுறைகளிலிருந்தும் வேறுபடுத்துகின்றன.

1. குறியீட்டை ஒருமுறை உருவாக்கி, எப்போதும் இயக்கவும்

HydraType உங்கள் இலக்கு கிளாஸை ஒருமுறை மட்டுமே ஆய்வு செய்து, அதன் துல்லியமான வடிவத்திற்கு ஏற்ப ஒரு பிரத்யேக PHP கிளாஸை எழுதுகிறது. உங்கள் DTO-வில் ஒரு public string $name, ஒரு int $status, மற்றும் ஒரு private DateTime $createdAt இருந்தால், உருவாக்கப்பட்ட ரைட்டர் இவை அனைத்தையும் முன்கூட்டியே அறிந்து கொள்ளும். ரன்டைமில் மீண்டும் மீண்டும் ரிஃப்ளெக்ஷன் செய்வதோ, ஸ்ட்ரிங் பார்சிங் (string parsing) செய்வதோ அல்லது மெட்டாடேட்டாவை உருவாக்குவதோ இருக்காது. OPCache உருவாக்கப்பட்ட கோப்பைத் தொகுத்த (compile) பிறகு, இந்த Hydrator கையால் எழுதப்பட்ட PHP-யிலிருந்து வேறுபடுத்திப் பார்க்க முடியாது.

2. காலியான பைப்லைன்களை நீக்குதல்

பெரும்பாலான ஹைட்ரேட்டர்கள் தங்கள் வேலையை ரன்டைம் பைப்லைனைச் சுற்றி கட்டமைக்கின்றன. அவை மியூட்டேட்டர்கள், வேலிடேட்டர்கள், டிரான்ஸ்ஃபார்மர்கள் போன்ற callables-களின் ஒரு அரேவை பராமரிக்கின்றன மற்றும் ஒவ்வொரு மதிப்பையும் சுழற்சி செய்கின்றன. அந்த அரேக்கள் காலியாக இருந்தாலும் கூட, foreach அல்லது array_reduce இயங்கும். HydraType இதை முற்றிலும் நீக்குகிறது. குறியீடு உருவாக்கத்தின் போது, ஒரு பண்பிற்கு உண்மையில் என்ன தேவை என்பதை இது சரிபார்க்கிறது. செயல்பாடுகளின் பட்டியல் காலியாக இருந்தால், அது $object->name = $data['name']; போன்ற ஒரு நேரடி ஒதுக்கீட்டை மட்டுமே வழங்கும். ஜெனரேட்டர் ஏற்கனவே முடிவெடுத்துவிட்டதால், ரன்டைமில் எந்தச் சுழற்சியும் நடக்காது.

3. ரிஃப்ளெக்ஷன் எழுதுவதைத் தவிர்க்க Closure Scoping-ஐப் பயன்படுத்துதல்

வெளிப்படையான மேப்பர்களுக்கு (external mappers) பிரைவேட் பண்புகள் (private properties) ஒரு பெரிய தலைவலி. வழக்கமான தீர்வு ReflectionProperty::setValue(), ஆனால் நேரடி அணுகலை விட அந்த முறை அதிக சுமையை ஏற்படுத்துகிறது. HydraType Closure::bind-ஐப் பயன்படுத்தி இதைத் தவிர்க்கிறது. உருவாக்கப்பட்ட ரைட்டரில் இலக்கு கிளாஸிற்கு உட்பட்ட (scoped) closures உள்ளன, இது ரிஃப்ளெக்ஷன் இல்லாமலேயே private மற்றும் protected பண்புகளை நேரடியாக அணுக அனுமதிக்கிறது. இந்த பைண்டிங் (binding) ஒருமுறை மட்டுமே நடக்கும்; அதன் பிறகு, closure கிட்டத்தட்ட இயல்பான வேகத்தில் (near-native speed) இயங்கும்.

4. ரைட்டரை மீண்டும் பயன்படுத்துதல்

சில லைப்ரரிகள் ஒவ்வொரு ஆப்ஜெக்ட்டிற்கும் ஒரு புதிய ஸ்ட்ரேட்டஜி (strategy) அல்லது ரிஃப்ளெக்ஷன் கிராஃபை உருவாக்குகின்றன. HydraType அதன் ரைட்டரை ஒருமுறை உருவாக்கி, பின்னர் ஆயிரக்கணக்கான ஆப்ஜெக்ட்களுக்கு அந்த இன்ஸ்டன்ஸ்ஸையே (instance) மீண்டும் பயன்படுத்துகிறது. இதனால் ஒவ்வொரு ஆப்ஜெக்ட்டிற்கும் ஏற்படும் செலவு, பண்பு மதிப்புகளை அமைத்து முடிவை வழங்கும் மிகக் குறைந்த அளவாகக் குறைகிறது.

புள்ளிவிவரங்கள்

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 என்பது format detection, normalizers மற்றும் deep object graphs ஆகியவற்றைக் கையாளும் ஒரு serializer component-இன் ஒரு பகுதியாகும். Valinor என்பது type trees மற்றும் coercion ஆகியவற்றில் மிகவும் துல்லியமானது. அந்த வலிமை ஒரு விலையுடன் வருகிறது. இந்தச் சோதனையில், அவை HydraType-ஐ விட ஏறத்தாழ முப்பது முதல் நாற்பது மடங்கு மெதுவாக இருந்தன. ஏற்கனவே code generation-ஐப் பயன்படுத்தும் Ocramius GeneratedHydrator கூட, pipelines மற்றும் property access தொடர்பான கட்டமைப்பு வேறுபாடுகள் காரணமாகச் சற்று பின் தங்கியுள்ளது.

சூழல் முக்கியமானது. ஒரு ஒற்றை database query அல்லது ஒரு HTTP roundtrip இந்த எண்களையெல்லாம் மிகச் சிறியதாக ஆக்கிவிடும். ஒரு query-க்குப் பிறகு நீங்கள் மூன்று objects-களை மட்டுமே hydrate செய்கிறீர்கள் என்றால், nanoseconds-களைத் துரத்துவது ஆற்றல் வீணாகும் செயலாகும். நீங்கள் அளவை அதிகரிக்கும் போது (scale) இந்த இடைவெளி முக்கியத்துவம் பெறுகிறது. ஐம்பதாயிரம் பதிவுகளை (records) hydrate செய்யும் ஒரு batch process, அல்லது cache arrays-லிருந்து objects-களை மீண்டும் உருவாக்கும் ஒரு queue consumer, 267 nanoseconds மற்றும் பத்து microseconds-க்கு இடையிலான வித்தியாசத்தை உணர முடியும். புலங்கள் (fields) மற்றும் வரிசைகளை (rows) பெருக்கிக் கணக்கிடும்போது, வேகமான mapper எந்தவொரு business logic-ஐயும் மாற்றாமல் ஒரு வேலையின் நேரத்தை பல வினாடிகள் குறைக்க முடியும்.

முக்கியக் கருத்து

இங்கு நாம் கற்றுக்கொள்ள வேண்டிய பாடம், ஒவ்வொரு திட்டத்திற்கும் (project) ஒரு தனிப்பயனாக்கப்பட்ட (custom) hydrator தேவை என்பது அல்ல. மாறாக, கட்டமைப்புத் தேர்வுகள் (architecture choices) எவ்வாறு தாக்கத்தை ஏற்படுத்துகின்றன என்பதே ஆகும். நீங்கள் கேட்பதை மட்டும் உள்ளடக்கிய code-ஐ உருவாக்குவதன் மூலம், HydraType சிக்கலான பண்புகளுக்கு ஒரு மாற்று வழியை (escape hatch) வழங்கும் அதே வேளையில், சாதாரண பண்புகளை (plain properties) வேகமாகவும் வைத்திருக்கிறது. Type conversion மற்றும் nested objects உங்களுக்குத் தேவைப்படும்போது மட்டுமே செயல்படும், ஆனால் அவை பொருளில் உள்ள ஒவ்வொரு scalar string-க்கும் செயல்திறனைப் பாதிப்பதில்லை.

நீங்கள் கருவிகளை மாற்றும் முன், profile செய்யுங்கள். உங்கள் நிஜ உலகச் செயல்பாடுகளில் (real-world traces) hydration ஒரு பெரிய தாக்கத்தை ஏற்படுத்தவில்லை என்றால், சரிசெய்ய ஏதுமில்லை. ஆனால், நீங்கள் பொதுவான mappers மூலம் பெரிய தரவுத் தொகுப்புகளை (large datasets) அனுப்பும்போது CPU பயன்பாடு அதிகரிப்பதைக் கவனித்தால், சாதாரண PHP assignment இன்னும் இருப்பதை நினைவில் கொள்ளுங்கள். சில நேரங்களில், மிக வேகமான code என்பது நீங்கள் நீங்களாகவே எழுதிய, ஒருமுறை உருவாக்கிவிட்டு மறந்துவிடும் எளிமையான code-ஆகத்தான் இருக்கும்.