ऑब्जेक्ट हाइड्रेशन एक सुलझी हुई समस्या लगती है जब तक कि आप इसे मापते नहीं हैं। आप डेटाबेस से एक रो (row) फेच करते हैं, ऐरे को एक ऑब्जेक्ट में मैप करते हैं, और आगे बढ़ जाते हैं। हम में से अधिकांश इसे प्लंबिंग की तरह मानते हैं—अदृश्य, उबाऊ, और यह मान लिया जाता है कि यह पर्याप्त तेज़ है। फिर एक दिन आप किसी बैच जॉब या क्यू वर्कर को प्रोफाइल करते हैं और देखते हैं कि CPU समय का एक आश्चर्यजनक हिस्सा मैपर में गायब हो जाता है। यही अहसास HydraType के निर्माण का कारण बना, एक ऐसा हाइड्रेटर जो एक जिद्दी नियम के इर्द-गिर्द बनाया गया है: एक क्लास को कभी भी उन फीचर्स के लिए भुगतान नहीं करना चाहिए जिनका उसकी प्रॉपर्टीज उपयोग नहीं करती हैं।

यदि कोई फ़ील्ड एक साधारण स्ट्रिंग है जिसे किसी भी ट्रांसफ़ॉर्मेशन की आवश्यकता नहीं है, तो जेनरेट किया गया कोड ऐसा दिखना चाहिए जैसे उसे किसी इंसान ने हाथ से लिखा हो। डायरेक्ट असाइनमेंट। कोई लूप नहीं, कोई पाइपलाइन नहीं, कोई रिफ्लेक्शन नहीं।

हाइड्रेशन की वास्तविक लागत क्या है

पहली नज़र में, ['name' => 'Alice', 'age' => 30] को User ऑब्जेक्ट में बदलना मामूली लगता है। समस्या तब शुरू होती है जब आप लचीलापन (flexibility) चाहते हैं। अधिकांश सामान्य-उद्देश्य वाले हाइड्रेटर्स रनटाइम पर क्लासों का निरीक्षण करने के लिए रिफ्लेक्शन (reflection) पर निर्भर करते हैं। वे मेटाडेटा मैप बनाते हैं, टाइप कोएर्शन (type coercion) को नेविगेट करते हैं, और नेस्टेड ऑब्जेक्ट ग्राफ्स को हल करते हैं। उन्हें पाइपलाइन्स भी बहुत पसंद हैं। हर वैल्यू म्यूटेटर्स, एसेर्शन और ट्रांसफॉर्मर्स के एक अनुक्रम से होकर गुजरती है, भले ही वह अनुक्रम खाली ही क्यों न हो। लूप स्ट्रक्चर अपने आप में ओवरहेड जोड़ता है।

कुछ दर्जन रिकॉर्ड फेच करें तो आपको कभी पता नहीं चलेगा। लेकिन आधुनिक PHP एप्लिकेशन नियमित रूप से हजारों क्यू मैसेज प्रोसेस करते हैं, विशाल CSV सेट इम्पोर्ट करते हैं, या Elasticsearch से गहरे रिजल्ट सेट को हाइड्रेट करते हैं। उन परिदृश्यों में, एक मैपर जो प्रति फ़ील्ड कुछ माइक्रोसेकंड भी खर्च करता है, वह एक वास्तविक बॉटलनेक (bottleneck) बन जाता है। आठ फ़ील्ड वाले एक हजार ऑब्जेक्ट्स का मतलब है एक ऐसा टैक्स जो आप महसूस करेंगे।

ज़ीरो-ओवरहेड फिलॉसफी

HydraType के पीछे का मुख्य विचार फीचर आइसोलेशन (feature isolation) है। टाइप कन्वर्जन, नेस्टेड ऑब्जेक्ट हाइड्रेशन और एसेर्शन सभी समर्थित हैं, फिर भी उनमें से कोई भी अनिवार्य नहीं है। यदि किसी प्रॉपर्टी को ऐरे से ऑब्जेक्ट में सीधे कॉपी के अलावा किसी और चीज़ की आवश्यकता नहीं है, तो जेनरेट किया गया राइटर ठीक वही करता है। यहाँ कोई साझा पाइपलाइन नहीं है जो एक साधारण स्केलर फ़ील्ड को उन वैलिडेटर्स के पीछे लाइन में खड़े होने के लिए मजबूर करे जिनकी उसे आवश्यकता नहीं है।

इसे हासिल करना जितना लगता है उससे कहीं अधिक कठिन है। कई लाइब्रेरीज़ हर फ़ील्ड के लिए एक ही एग्जीक्यूशन पाथ साझा करती हैं क्योंकि इससे कोडबेस छोटा और एकसमान रहता है। HydraType इसके विपरीत दृष्टिकोण अपनाता है। यह प्रत्येक लक्षित DTO के लिए एक अद्वितीय PHP क्लास जेनरेट करता है, जो सटीक रूप से वही ऑपरेशन्स उत्सर्जित (emit) करता है जिनकी उस विशिष्ट क्लास को आवश्यकता होती है। जटिलता 'ऑप्ट-इन' (opt-in) बन जाती है, न कि हर प्रॉपर्टी पर लगने वाला एक अनिवार्य टैक्स।

यह गति कैसे प्राप्त होती है

चार ठोस विकल्प HydraType को जेनेरिक रिफ्लेक्शन-आधारित टूल्स और अन्य जेनरेटेड अप्रोच दोनों से अलग करते हैं।

1. एक बार कोड जेनरेट करें, इसे हमेशा के लिए चलाएं

HydraType आपकी लक्षित क्लास का केवल एक बार निरीक्षण करता है, और फिर उसके सटीक आकार के अनुरूप एक समर्पित PHP क्लास लिखता है। यदि आपके DTO में एक पब्लिक स्ट्रिंग $name, एक int $status, और एक प्राइवेट DateTime $createdAt है, तो जेनरेट किया गया राइटर पहले से ही यह सब जानता है। रनटाइम के दौरान कोई बार-बार रिफ्लेक्शन, कोई स्ट्रिंग पार्सिंग, और कोई लेज़ी मेटाडेटा बिल्डिंग नहीं होती है। एक बार जब OPCache जेनरेट की गई फ़ाइल को कंपाइल कर देता है, तो हाइड्रेटर हाथ से लिखे गए PHP से अलग नहीं लगता।

2. खाली पाइपलाइन्स को खत्म करें

अधिकांश हाइड्रेटर्स अपने काम को रनटाइम पाइपलाइन के इर्द-गिर्द व्यवस्थित करते हैं। वे कॉलएबल्स (callables)—म्यूटेटर्स, वैलिडेटर्स, ट्रांसफॉर्मर्स—का एक ऐरे बनाए रखते हैं और हर वैल्यू पर इटरेट करते हैं। भले ही वे ऐरे खाली हों, foreach या array_reduce फिर भी निष्पादित होता है। HydraType इसे पूरी तरह से हटा देता है। कोड जनरेशन के दौरान यह जाँचता है कि किसी प्रॉपर्टी को वास्तव में क्या चाहिए। यदि ऑपरेशन लिस्ट खाली है, तो यह एक साधारण असाइनमेंट उत्सर्जित करता है जैसे $object->name = $data['name'];। रनटाइम कभी भी किसी चीज़ पर इटरेट नहीं करता है, क्योंकि जनरेटर ने पहले ही सोच लिया है।

3. रिफ्लेक्शन राइट्स को खत्म करने के लिए क्लोजर स्कोपिंग का उपयोग करें

प्राइवेट प्रॉपर्टीज़ बाहरी मैपर्स के लिए एक क्लासिक सिरदर्द हैं। सामान्य समाधान ReflectionProperty::setValue() है, लेकिन उस मेथड में डायरेक्ट प्रॉपर्टी एक्सेस की तुलना में भारी पेनल्टी लगती है। HydraType Closure::bind का उपयोग करके इससे बचता है। जेनरेट किया गया राइटर लक्षित क्लास के स्कोप में क्लोजर (closures) रखता है, जो उन्हें रिफ्लेक्शन के बिना सीधे प्राइवेट और प्रोटेक्टेड प्रॉपर्टीज़ को छूने की अनुमति देता है। बाइंडिंग कंस्ट्रक्शन के दौरान एक बार होती है; उसके बाद, क्लोजर लगभग नेटिव स्पीड पर चलता है।

4. राइटर का पुन: उपयोग करें

कुछ लाइब्रेरीज़ प्रत्येक ऑब्जेक्ट जिसे वे हाइड्रेट करते हैं, उसके लिए एक नया स्ट्रैटेजी या रिफ्लेक्शन ग्राफ इंस्टेंटिएट करती हैं। HydraType अपना राइटर एक बार बनाता है, और फिर हजारों ऑब्जेक्ट्स में उसी इंस्टेंस का पुन: उपयोग करता है। प्रति-ऑब्जेक्ट लागत घटकर प्रॉपर्टी वैल्यू सेट करने और परिणाम लौटाने के न्यूनतम स्तर तक आ जाती है।

संख्याएँ

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 एक सीरियलाइज़र कंपोनेंट का हिस्सा है जो फॉर्मेट डिटेक्शन, नॉर्मलाइज़र और डीप ऑब्जेक्ट ग्राफ को संभालता है। Valinor टाइप ट्रीज़ और कोअर्शन (coercion) के मामले में बहुत सख्त है। उस शक्ति की एक कीमत होती है। इस परीक्षण में, वे HydraType की तुलना में लगभग तीस से चालीस गुना धीमे थे। यहाँ तक कि Ocramius GeneratedHydrator भी, जो पहले से ही कोड जनरेशन का उपयोग करता है, पाइपलाइनों और प्रॉपर्टी एक्सेस के आसपास के आर्किटेक्चरल अंतरों के कारण थोड़ा पीछे रह जाता है।

संदर्भ मायने रखता है। एक सिंगल डेटाबेस क्वेरी या एक HTTP राउंडट्रिप इन सभी नंबरों को बौना बना देती है। यदि आप एक क्वेरी के बाद तीन ऑब्जेक्ट्स को हाइड्रेट कर रहे हैं, तो नैनोसेकंड्स के पीछे भागना ऊर्जा की बर्बादी है। यह अंतर तब महत्वपूर्ण हो जाता है जब आप स्केल करते हैं। पचास हजार रिकॉर्ड्स को हाइड्रेट करने वाली एक बैच प्रक्रिया, या कैश एरेज़ से ऑब्जेक्ट्स को फिर से बनाने वाला एक क्यू कंज्यूमर, 267 नैनोसेकंड और दस माइक्रोसेकंड के बीच के अंतर को महसूस करेगा। फ़ील्ड्स और रोज़ (rows) के हिसाब से इसे गुणा करें, और तेज़ मैपर बिना किसी बिज़नेस लॉजिक को बदले किसी काम से कई सेकंड बचा सकता है।

मुख्य निष्कर्ष

यहाँ सबक यह नहीं है कि हर प्रोजेक्ट को एक कस्टम हाइड्रेटर की आवश्यकता है। सबक यह है कि आर्किटेक्चर के चुनाव का प्रभाव बढ़ता जाता है (compound)। केवल वही कोड जनरेट करके जिसकी आपने मांग की है, HydraType सादे प्रॉपर्टीज़ को तेज़ रखता है और साथ ही जटिल प्रॉपर्टीज़ के लिए एक 'एस्केप हैच' (escape hatch) भी प्रदान करता है। टाइप कन्वर्जन और नेस्टेड ऑब्जेक्ट्स तब मौजूद होते हैं जब आपको उनकी आवश्यकता होती है, लेकिन वे ऑब्जेक्ट पर मौजूद हर स्केलर स्ट्रिंग के लिए परफॉरमेंस पर बोझ नहीं डालते हैं।

टूल बदलने से पहले, प्रोफाइलिंग करें। यदि वास्तविक दुनिया के ट्रेसेस (traces) में हाइड्रेशन दिखाई नहीं देता है, तो ठीक करने के लिए कुछ भी नहीं है। लेकिन यदि आप जेनेरिक मैपर्स के माध्यम से बड़े डेटासेट भेज रहे हैं और CPU के बढ़ते उपयोग को देख रहे हैं, तो याद रखें कि सादा PHP असाइनमेंट अभी भी मौजूद है। कभी-कभी सबसे तेज़ कोड वही होता है जो आपने खुद लिखा होता, जिसे एक बार जनरेट किया गया और फिर भुला दिया गया।