JIT कंपाइलर की कमी: NYI से क्यों बचें?

API7.ai

September 30, 2022

OpenResty (NGINX + Lua)

पिछले लेख में, हमने LuaJIT में FFI को देखा। यदि आपका प्रोजेक्ट केवल OpenResty द्वारा प्रदान किए गए API का उपयोग करता है और आपको C फ़ंक्शन को कॉल करने की आवश्यकता नहीं है, तो FFI आपके लिए इतना महत्वपूर्ण नहीं है। आपको केवल यह सुनिश्चित करने की आवश्यकता है कि lua-resty-core सक्षम है।

लेकिन LuaJIT में NYI, जिसके बारे में हम आज बात करेंगे, एक महत्वपूर्ण समस्या है जिससे हर इंजीनियर जो OpenResty का उपयोग करता है, बच नहीं सकता, और यह प्रदर्शन को महत्वपूर्ण रूप से प्रभावित करता है।

आप OpenResty का उपयोग करके तार्किक रूप से सही कोड जल्दी से लिख सकते हैं, लेकिन NYI को समझे बिना, आप कुशल कोड नहीं लिख सकते और OpenResty की शक्ति का लाभ नहीं उठा सकते। दोनों के बीच प्रदर्शन का अंतर कम से कम एक आदेश का होता है।

NYI क्या है?

आइए पहले एक बिंदु को याद करें जिसे हमने पहले बताया था।

LuaJIT का रनटाइम, Lua इंटरप्रेटर के एक असेंबली कार्यान्वयन के अलावा, एक JIT कंपाइलर भी है जो सीधे मशीन कोड उत्पन्न कर सकता है।

LuaJIT में JIT कंपाइलर का कार्यान्वयन अभी तक पूरा नहीं हुआ है। यह कुछ फ़ंक्शन को कंपाइल नहीं कर सकता क्योंकि उन्हें लागू करना चुनौतीपूर्ण है और क्योंकि LuaJIT के लेखक अभी सेमी-रिटायर्ड हैं। इनमें सामान्य pairs() फ़ंक्शन, unpack() फ़ंक्शन, Lua CFunction कार्यान्वयन पर आधारित Lua C मॉड्यूल, और इसी तरह के अन्य शामिल हैं। यह JIT कंपाइलर को इंटरप्रेटर मोड में वापस जाने की अनुमति देता है जब यह वर्तमान कोड पथ पर एक ऐसे ऑपरेशन का सामना करता है जिसे यह समर्थन नहीं करता है।

LuaJIT की आधिकारिक वेबसाइट पर इन NYIs की एक पूरी सूची है, और मैं सुझाव देता हूं कि आप इसे देखें। लेख का लक्ष्य यह नहीं है कि आप इस सूची को याद करें, बल्कि यह है कि आप कोड लिखते समय इसे सचेत रूप से याद रखें।

नीचे, मैंने NYI सूची से स्ट्रिंग लाइब्रेरी के लिए कुछ फ़ंक्शन लिए हैं।

string library

string.byte का कंपाइल स्थिति हां है, जिसका अर्थ है कि इसे JIT के साथ अनुकूलित किया जा सकता है, और आप इसे अपने कोड में बिना डर के उपयोग कर सकते हैं।

string.char का कंपाइल स्थिति 2.1 है, जिसका अर्थ है कि यह LuaJIT 2.1 से समर्थित है। जैसा कि हम जानते हैं, OpenResty में LuaJIT, LuaJIT 2.1 पर आधारित है, इसलिए आप इसे सुरक्षित रूप से उपयोग कर सकते हैं।

string.dump का कंपाइल स्थिति कभी नहीं है, यानी इसे JIT के साथ अनुकूलित नहीं किया जाएगा और यह इंटरप्रेटर मोड में वापस जाएगा। अब तक, भविष्य में इसे समर्थन देने की कोई योजना नहीं है।

string.find का कंपाइल स्थिति 2.1 आंशिक है, जिसका अर्थ है कि यह LuaJIT 2.1 से आंशिक रूप से समर्थित है, और उसके बाद के नोट में कहा गया है कि यह केवल निश्चित स्ट्रिंग्स की खोज करता है, पैटर्न मिलान नहीं। इसलिए निश्चित स्ट्रिंग्स की खोज के लिए, string.find को JIT के साथ अनुकूलित किया जा सकता है।

स्वाभाविक रूप से, हमें NYI का उपयोग करने से बचना चाहिए ताकि हमारा अधिक कोड JIT-कंपाइल हो सके और प्रदर्शन सुनिश्चित हो सके। हालांकि, वास्तविक दुनिया के वातावरण में, हम कभी-कभी कुछ NYI फ़ंक्शन का उपयोग करने के लिए मजबूर होते हैं, तो हमें क्या करना चाहिए?

NYI के विकल्प

चिंता न करें। अधिकांश NYI फ़ंक्शन हम सम्मानपूर्वक छोड़ सकते हैं और उनके कार्यक्षमता को अन्य तरीकों से लागू कर सकते हैं। आगे, मैंने कुछ विशिष्ट NYIs को चुना है और उनके विकल्पों के प्रकारों को समझाने के लिए चुना है। इस तरह, आप अन्य NYIs के बारे में भी जान सकते हैं।

string.gsub()

आइए पहले string.gsub() फ़ंक्शन को देखें, जो Lua का अंतर्निहित स्ट्रिंग मैनिपुलेशन फ़ंक्शन है जो ग्लोबल स्ट्रिंग प्रतिस्थापन करता है, जैसे कि निम्नलिखित उदाहरण।

$ resty -e 'local new = string.gsub("banana", "a", "A"); print(new)' bAnAnA

यह फ़ंक्शन एक NYI फ़ंक्शन है और इसे JIT द्वारा कंपाइल नहीं किया जा सकता।

हम OpenResty के API में एक प्रतिस्थापन फ़ंक्शन ढूंढने का प्रयास कर सकते हैं, लेकिन अधिकांश लोगों के लिए सभी APIs और उनके उपयोग को याद रखना व्यावहारिक नहीं है। इसलिए मैं हमेशा अपने विकास कार्य में lua-nginx-module के GitHub डॉक्यूमेंटेशन पेज को खोलता हूं।

उदाहरण के लिए, हम डॉक्यूमेंटेशन पेज पर gsub को एक कीवर्ड के रूप में खोज सकते हैं, और ngx.re.gsub याद आएगा।

हम पहले सुझाए गए restydoc टूल का उपयोग करके OpenResty API को खोज सकते हैं। आप इसे gsub के लिए खोजने का प्रयास कर सकते हैं।

$ restydoc -s gsub

जैसा कि आप देख सकते हैं, हमारे अपेक्षित ngx.re.gsub के बजाय, Lua के फ़ंक्शन दिखाए जाते हैं। वास्तव में, इस स्तर पर, restydoc एक सटीक अद्वितीय मिलान लौटाता है, इसलिए यदि आप API नाम स्पष्ट रूप से जानते हैं तो इसका उपयोग करना अधिक उपयुक्त है। फ़ज़ी खोज के लिए, आपको अभी भी डॉक्यूमेंटेशन में मैन्युअल रूप से करना होगा।

खोज परिणामों पर वापस जाकर, हम देखते हैं कि ngx.re.gsub फ़ंक्शन की परिभाषा निम्नलिखित है:

newstr, n, err = ngx.re.gsub(subject, regex, replace, options?)

यहां, फ़ंक्शन पैरामीटर और रिटर्न वैल्यूज़ को विशिष्ट अर्थों के साथ नामित किया गया है। वास्तव में, OpenResty में, मैं आपको बहुत सारे टिप्पणियाँ लिखने की सलाह नहीं देता। अधिकांश समय, एक अच्छा नाम कई लाइनों की टिप्पणियों से बेहतर होता है।

OpenResty रेगुलर सिस्टम से अपरिचित इंजीनियरों के लिए, आप अंत में वेरिएबल options देखकर भ्रमित हो सकते हैं। हालांकि, इस वेरिएबल की व्याख्या इस फ़ंक्शन में नहीं है बल्कि ngx.re.match फ़ंक्शन के डॉक्यूमेंटेशन में है।

यदि आप options के डॉक्यूमेंटेशन को देखेंगे, तो आप देखेंगे कि यदि हम इसे jo पर सेट करते हैं, तो यह PCRE JIT को चालू कर देता है, ताकि ngx.re.gsub का उपयोग करने वाला कोड LuaJIT के साथ-साथ PCRE JIT द्वारा भी JIT-कंपाइल हो सके।

मैं डॉक्यूमेंटेशन के विवरण में नहीं जाऊंगा। OpenResty डॉक्यूमेंटेशन बहुत अच्छा है, इसलिए इसे ध्यान से पढ़ें और आप अपनी अधिकांश समस्याओं को हल कर सकते हैं।

string.find()

string.gsub के विपरीत, string.find सादे मोड में (यानी स्ट्रिंग खोज) JIT-योग्य है, जबकि string.find नियमितता के साथ स्ट्रिंग खोज के लिए JIT-योग्य नहीं है, जो OpenResty के API ngx.re.find का उपयोग करके किया जाता है।

इसलिए, जब आप OpenResty में स्ट्रिंग खोज करते हैं, तो आपको पहले स्पष्ट रूप से यह पहचानना होगा कि आप एक निश्चित स्ट्रिंग या एक रेगुलर एक्सप्रेशन की खोज कर रहे हैं। यदि यह पूर्व है, तो string.find का उपयोग करें और अंत में plain को true पर सेट करना याद रखें।

string.find("foo bar", "foo", 1, true)

बाद के मामले में, आपको OpenResty के API का उपयोग करना चाहिए और PCRE के लिए JIT विकल्प को चालू करना चाहिए।

ngx.re.find("foo bar", "^foo", "jo")

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

unpack()

तीसरा फ़ंक्शन जिसे हम देखेंगे वह unpack() है। unpack() भी एक ऐसा फ़ंक्शन है जिससे बचना चाहिए, विशेष रूप से लूप बॉडी में नहीं। इसके बजाय, आप इसे एक सरणी के इंडेक्स नंबरों का उपयोग करके एक्सेस कर सकते हैं, जैसा कि निम्नलिखित कोड के उदाहरण में है।

$ resty -e ' local a = {100, 200, 300, 400} for i = 1, 2 do print(unpack(a)) end' $ resty -e 'local a = {100, 200, 300, 400} for i = 1, 2 do print(a[1], a[2], a[3], a[4]) end'

आइए unpack को थोड़ा गहराई से समझें, और इस बार हम restydoc का उपयोग करके खोज सकते हैं।

$ restydoc -s unpack

जैसा कि आप unpack डॉक्यूमेंटेशन से देख सकते हैं, unpack(list [, i [, j]]) return list[i], list[i+1], list[j] के बराबर है, और आप unpack को सिंटैक्टिक शुगर के रूप में सोच सकते हैं। इस तरह, आप इसे एक सरणी इंडेक्स के रूप में एक्सेस कर सकते हैं बिना LuaJIT के JIT कंपाइलेशन को तोड़े।

pairs()

अंत में, आइए हैश टेबल को ट्रैवर्स करने वाले pairs() फ़ंक्शन को देखें, जो JIT द्वारा कंपाइल नहीं किया जा सकता।

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

इन चार उदाहरणों को कहने के बाद, आइए संक्षेप में बताएं कि NYI फ़ंक्शन के उपयोग से बचने के लिए, आपको इन दो बिंदुओं पर ध्यान देना होगा।

  • Lua के स्टैंडर्ड लाइब्रेरी फ़ंक्शन के बजाय OpenResty द्वारा प्रदान किए गए API का उपयोग करें। याद रखें कि Lua एक एम्बेडेड भाषा है, और हम OpenResty में प्रोग्रामिंग कर रहे हैं, Lua में नहीं।
  • यदि आपको अंतिम उपाय के रूप में NYI भाषा का उपयोग करना ही है, तो कृपया सुनिश्चित करें कि यह कोड हॉट पथ पर नहीं है।

NYI का पता कैसे लगाएं?

NYI से बचने के बारे में यह सब बात करने का उद्देश्य आपको यह सिखाना है कि क्या करना है। हालांकि, यदि यह यहां अचानक समाप्त हो जाता है, तो यह OpenResty के दर्शन में से एक के साथ असंगत होगा।

मशीन द्वारा स्वचालित रूप से क्या किया जा सकता है, उसमें मनुष्य को शामिल नहीं करना चाहिए।

लोग मशीन नहीं हैं, और हमेशा चूक होती है। कोड में उपयोग किए गए NYI का स्वचालित रूप से पता लगाना एक इंजीनियर के मूल्य का एक आवश्यक प्रतिबिंब है।

यहां मैं LuaJIT के साथ आने वाले jit.dump और jit.v मॉड्यूल की सिफारिश करता हूं। ये दोनों JIT कंपाइलर के काम करने की प्रक्रिया को प्रिंट करते हैं। पूर्व विस्तृत जानकारी आउटपुट करता है जिसका उपयोग LuaJIT को डीबग करने के लिए किया जा सकता है। आप इसके सोर्स कोड को गहराई से समझने के लिए संदर्भित कर सकते हैं; बाद का आउटपुट अधिक सरल है, प्रत्येक लाइन एक ट्रेस से मेल खाती है, और आमतौर पर यह जांचने के लिए उपयोग किया जाता है कि क्या यह JIT हो सकता है।

हमें यह कैसे करना चाहिए? हम init_by_lua में निम्नलिखित दो लाइन कोड जोड़कर शुरू कर सकते हैं।

local v = require "jit.v" v.on("/tmp/jit.log")

फिर, अपने स्ट्रेस टेस्ट टूल या कुछ सौ यूनिट टेस्ट सेट चलाएं ताकि LuaJIT को JIT कंपाइलेशन को ट्रिगर करने के लिए पर्याप्त गर्म किया जा सके। एक बार यह हो जाने के बाद, /tmp/jit.log के परिणामों की जांच करें।

बेशक, यह दृष्टिकोण अपेक्षाकृत थकाऊ है, इसलिए यदि आप चीजों को सरल रखना चाहते हैं, तो resty पर्याप्त है, और OpenResty CLI निम्नलिखित विकल्पों के साथ आता है।

$resty -j v -e 'for i=1, 1000 do local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", "[$0,$1]", "i") end' [TRACE 1 (command line -e):1 stitch C:107bc91fd] [TRACE 2 (1/stitch) (command line -e):2 -> 1]

जहां resty में -j LuaJIT से संबंधित विकल्प है, मान dump और v हैं, जो jit.dump और jit.v मोड को चालू करने से मेल खाते हैं।

jit.v मॉड्यूल के आउटपुट में, प्रत्येक लाइन एक सफलतापूर्वक कंपाइल किया गया ट्रेस ऑब्जेक्ट है। अभी एक JIT-योग्य ट्रेस का उदाहरण है, और यदि NYI फ़ंक्शन का सामना होता है, तो आउटपुट में यह निर्दिष्ट किया जाएगा कि वे NYIs हैं, जैसा कि निम्नलिखित pairs के उदाहरण में है।

$resty -j v -e 'local t = {} for i=1,100 do t[i] = i end for i=1, 1000 do for j=1,1000 do for k,v in pairs(t) do -- end end end'

यह JIT नहीं हो सकता, इसलिए परिणाम लाइन 8 में एक NYI फ़ंक्शन को इंगित करता है।

[TRACE 1 (command line -e):2 loop] [TRACE --- (command line -e):7 -- NYI: bytecode 72 at (command line -e):8]

अंत में लिखें

यह पहली बार है जब हमने OpenResty प्रदर्शन मुद्दों के बारे में अधिक लंबाई में बात की है। NYI के बारे में इन अनुकूलनों को पढ़ने के बाद, आप क्या सोचते हैं? आप अपनी राय एक टिप्पणी में छोड़ सकते हैं।

अंत में, मैं आपको एक विचारोत्तेजक प्रश्न छोड़ता हूं जब string.find() फ़ंक्शन के विकल्पों पर चर्चा करते समय; मैंने उल्लेख किया कि एक परत लपेटना और अनुकूलन विकल्पों को डिफ़ॉल्ट रूप से चालू करना बेहतर होगा। इसलिए, मैं उस कार्य को आपके लिए एक छोटे टेस्ट ड्राइव के लिए छोड़ता हूं।

अपने उत्तर टिप्पणी अनुभाग में लिखने के लिए स्वतंत्र महसूस करें, और आप इस लेख को अपने सहयोगियों और दोस्तों के साथ साझा करने के लिए स्वागत करते हैं ताकि संवाद और प्रगति कर सकें।