lua-resty-core क्यों बेहतर प्रदर्शन करता है?
API7.ai
September 30, 2022
जैसा कि हमने पिछले दो पाठों में कहा था, Lua एक एम्बेडेड विकास भाषा है जो कोर को छोटा और संक्षिप्त रखती है। आप Lua को Redis और NGINX में एम्बेड कर सकते हैं ताकि यह आपको व्यावसायिक तर्क को लचीले ढंग से करने में मदद करे। Lua आपको मौजूदा C फ़ंक्शन्स और डेटा संरचनाओं को कॉल करने की भी अनुमति देता है ताकि पहिये को दोबारा न बनाना पड़े।
Lua में, आप C फ़ंक्शन्स को कॉल करने के लिए Lua C API का उपयोग कर सकते हैं, और LuaJIT में, आप FFI का उपयोग कर सकते हैं। OpenResty के लिए।
- कोर
lua-nginx-moduleमें, C फ़ंक्शन्स को कॉल करने के लिए API Lua C API का उपयोग करके किया जाता है। lua-resty-coreमें,lua-nginx-moduleमें पहले से मौजूद कुछ API को FFI मॉडल का उपयोग करके लागू किया गया है।
आप शायद सोच रहे होंगे कि हमें इसे FFI के साथ क्यों लागू करने की आवश्यकता है?
चिंता न करें। आइए ngx.base64_decode, एक सीधा API, को उदाहरण के रूप में लें और देखें कि Lua C API FFI कार्यान्वयन से कैसे भिन्न है। आप उनके प्रदर्शन की एक सहज समझ भी प्राप्त कर सकते हैं।
Lua CFunction
आइए देखें कि यह lua-nginx-module में Lua C API का उपयोग करके कैसे लागू किया गया है। हम प्रोजेक्ट के कोड में decode_base64 खोजते हैं और इसका कार्यान्वयन ngx_http_lua_string.c में पाते हैं।
lua_pushcfunction(L, ngx_http_lua_ngx_decode_base64); lua_setfield(L, -2, "decode_base64");
उपरोक्त कोड देखने में परेशानी भरा है, लेकिन सौभाग्य से, हमें lua_ से शुरू होने वाले दो फ़ंक्शन्स और उनके तर्कों की विशिष्ट भूमिका को समझने की आवश्यकता नहीं है; हमें केवल एक बात जानने की आवश्यकता है - यहां एक CFunction पंजीकृत है: ngx_http_lua_ngx_decode_base64, और यह ngx.base64_decode से मेल खाता है, जो सार्वजनिक रूप से उजागर किए गए API से मेल खाता है।
आइए आगे बढ़ें और "मानचित्र का अनुसरण करें" और इस C फ़ाइल में ngx_http_lua_ngx_decode_base64 खोजें, जो फ़ाइल की शुरुआत में परिभाषित है:
static int ngx_http_lua_ngx_decode_base64(lua_State *L);
उन C फ़ंक्शन्स के लिए जिन्हें Lua द्वारा कॉल किया जा सकता है, इसका इंटरफ़ेस Lua द्वारा आवश्यक फॉर्म का पालन करना चाहिए, जो typedef int (*lua_CFunction)(lua_State* L) है। इसमें lua_State प्रकार का एक पॉइंटर L तर्क के रूप में शामिल है; इसका रिटर्न मान प्रकार एक पूर्णांक है जो लौटाए गए मानों की संख्या को इंगित करता है, न कि रिटर्न मान को।
इसे निम्नानुसार लागू किया गया है (यहां, मैंने त्रुटि हैंडलिंग कोड को हटा दिया है)।
static int ngx_http_lua_ngx_decode_base64(lua_State *L) { ngx_str_t p, src; src.data = (u_char *) luaL_checklstring(L, 1, &src.len); p.len = ngx_base64_decoded_length(src.len); p.data = lua_newuserdata(L, p.len); if (ngx_decode_base64(&p, &src) == NGX_OK) { lua_pushlstring(L, (char *) p.data, p.len); } else { lua_pushnil(L); } return 1; }
इस कोड में मुख्य बात ngx_base64_decoded_length, और ngx_decode_base64 है, जो दोनों NGINX द्वारा प्रदान किए गए C फ़ंक्शन्स हैं।
हम जानते हैं कि C में लिखे गए फ़ंक्शन्स Lua कोड को रिटर्न मान पास नहीं कर सकते हैं, लेकिन Lua और C के बीच कॉल पैरामीटर्स और रिटर्न मान को स्टैक के माध्यम से पास करने की आवश्यकता होती है। यही कारण है कि यहां बहुत सारा कोड है जिसे हम पहली नज़र में समझ नहीं सकते हैं। साथ ही, इस कोड को JIT द्वारा ट्रैक नहीं किया जा सकता है, इसलिए LuaJIT के लिए, ये ऑपरेशन एक ब्लैक बॉक्स में हैं और इन्हें ऑप्टिमाइज़ नहीं किया जा सकता है।
LuaJIT FFI
FFI के विपरीत, FFI का इंटरैक्टिव भाग Lua में लागू किया गया है, जिसे JIT द्वारा ट्रैक और ऑप्टिमाइज़ किया जा सकता है; निश्चित रूप से, कोड भी अधिक संक्षिप्त और समझने में आसान है।
आइए base64_decode का उदाहरण लें, जिसका FFI कार्यान्वयन दो रिपॉजिटरीज़ में फैला हुआ है: lua-resty-core और lua-nginx-module, और आइए पहले वाले में लागू किए गए कोड को देखें।
ngx.decode_base64 = function (s) local slen = #s local dlen = base64_decoded_length(slen) local dst = get_string_buf(dlen) local pdlen = get_size_ptr() local ok = C.ngx_http_lua_ffi_decode_base64(s, slen, dst, pdlen) if ok == 0 then return nil end return ffi_string(dst, pdlen[0]) end
आप पाएंगे कि CFunction की तुलना में, FFI कार्यान्वयन का कोड बहुत अधिक ताज़ा है, इसका विशिष्ट कार्यान्वयन lua-nginx-module रिपॉजिटरी में ngx_http_lua_ffi_decode_base64 है। यदि आप यहां रुचि रखते हैं, तो आप इस फ़ंक्शन के प्रदर्शन की जांच स्वयं कर सकते हैं। यह बहुत सीधा है, मैं यहां कोड पोस्ट नहीं करूंगा।
हालांकि, यदि आप सावधान हैं, तो क्या आपने उपरोक्त कोड स्निपेट में कुछ फ़ंक्शन नामकरण नियम पाए हैं?
हां, OpenResty में सभी फ़ंक्शन्स के नामकरण नियम हैं, और आप उनके उपयोग को नामकरण से अनुमान लगा सकते हैं। उदाहरण के लिए:
ngx_http_lua_ffi_, FFI का उपयोग करके NGINX HTTP अनुरोधों को संभालने वाला Lua फ़ंक्शन।ngx_http_lua_ngx_, C फ़ंक्शन का उपयोग करके NGINX HTTP अनुरोधों को संभालने वाला Lua फ़ंक्शन।ngx_औरlua_से शुरू होने वाले अन्य फ़ंक्शन्स क्रमशः NGINX और Lua के लिए बिल्ट-इन फ़ंक्शन्स हैं।
इसके अलावा, OpenResty में C कोड का एक सख्त कोड स्पेसिफिकेशन है, और मैं यहां आधिकारिक C कोड स्टाइल गाइड पढ़ने की सलाह देता हूं। यह उन डेवलपर्स के लिए एक आवश्यक दस्तावेज़ है जो OpenResty के C कोड को सीखना चाहते हैं और PRs सबमिट करना चाहते हैं। अन्यथा, भले ही आपका PR अच्छी तरह से लिखा गया हो, आपको कोड स्टाइल मुद्दों के कारण बार-बार टिप्पणी करने और इसे बदलने के लिए कहा जाएगा।
FFI के बारे में अधिक API और विवरण के लिए, हम आपको आधिकारिक LuaJIT ट्यूटोरियल्स और दस्तावेज़ीकरण पढ़ने की सलाह देते हैं। तकनीकी कॉलम आधिकारिक दस्तावेज़ीकरण का विकल्प नहीं हैं; मैं केवल आपको सीखने के रास्ते को कम समय में, कम भटकाव के साथ इंगित कर सकता हूं; कठिन समस्याओं को अभी भी आपको हल करने की आवश्यकता है।
LuaJIT FFI GC
FFI का उपयोग करते समय, हम भ्रमित हो सकते हैं: FFI में अनुरोधित मेमोरी का प्रबंधन कौन करेगा? क्या हमें इसे C में मैन्युअल रूप से रिलीज़ करना चाहिए, या LuaJIT इसे स्वचालित रूप से रिक्लेम करेगा?
यहां एक सरल सिद्धांत है: LuaJIT केवल उन संसाधनों के लिए जिम्मेदार है जो इसके द्वारा आवंटित किए गए हैं; ffi।
उदाहरण के लिए, यदि आप ffi.C.malloc का उपयोग करके एक मेमोरी ब्लॉक अनुरोध करते हैं, तो आपको इसे जोड़ीदार ffi.C.free के साथ रिलीज़ करने की आवश्यकता होगी। आधिकारिक LuaJIT दस्तावेज़ीकरण में इसका एक उदाहरण है।
local p = ffi.gc(ffi.C.malloc(n), ffi.C.free) ... p = nil -- Last reference to p is gone. -- GC will eventually run finalizer: ffi.C.free(p)
इस कोड में, ffi.C.malloc(n) एक मेमोरी सेक्शन अनुरोध करता है, और ffi.gc एक डिस्ट्रक्टर कॉलबैक फ़ंक्शन ffi.C.free को पंजीकृत करता है, ffi.C.free तब स्वचालित रूप से कॉल किया जाएगा जब एक cdata p LuaJIT द्वारा GC'd हो जाता है ताकि C-स्तरीय मेमोरी को मुक्त किया जा सके। और cdata LuaJIT द्वारा GC'd होता है। LuaJIT उपरोक्त कोड में p को स्वचालित रूप से मुक्त कर देगा।
ध्यान दें कि यदि आप OpenResty में एक बड़े मेमोरी ब्लॉक का अनुरोध करना चाहते हैं, तो मैं ffi.C.malloc के बजाय ffi.new का उपयोग करने की सलाह देता हूं। कारण भी स्पष्ट हैं।
ffi.newcdataलौटाता है, जो LuaJIT द्वारा प्रबंधित मेमोरी का हिस्सा है।- LuaJIT GC की मेमोरी प्रबंधन की एक ऊपरी सीमा है, और OpenResty में LuaJIT में GC64 विकल्प सक्षम नहीं है। इसलिए एकल वर्कर के लिए मेमोरी की ऊपरी सीमा केवल 2G है। एक बार LuaJIT मेमोरी प्रबंधन की ऊपरी सीमा से अधिक हो जाने पर, यह एक त्रुटि का कारण बनेगा।
FFI का उपयोग करते समय, हमें मेमोरी लीक पर विशेष ध्यान देने की आवश्यकता है। हालांकि, हर कोई गलतियां करता है, और जब तक मनुष्य कोड लिखते हैं, तब तक बग्स हमेशा रहेंगे।
यहीं पर OpenResty की मजबूत परीक्षण और डिबगिंग टूलचेन काम आती है।
पहले परीक्षण के बारे में बात करते हैं। OpenResty सिस्टम में, हम मेमोरी लीक का पता लगाने के लिए Valgrind का उपयोग करते हैं।
हमने पिछले पाठ में उल्लेख किया परीक्षण ढांचा, test::nginx, में एक विशेष मेमोरी लीक डिटेक्शन मोड है जो यूनिट टेस्ट केस सेट चलाता है; आपको पर्यावरण चर TEST_NGINX_USE_VALGRIND=1 सेट करने की आवश्यकता है। आधिकारिक OpenResty प्रोजेक्ट इस मोड में पूरी तरह से पंजीकृत होगा जब संस्करण जारी किया जाएगा, और हम बाद में परीक्षण अनुभाग में और अधिक विवरण में जाएंगे। हम बाद में परीक्षण अनुभाग में और अधिक विवरण में जाएंगे।
OpenResty का CLI resty में भी --valgrind विकल्प है, जो आपको एक Lua कोड को अकेले चलाने की अनुमति देता है, भले ही आपने एक टेस्ट केस नहीं लिखा हो।
आइए डिबगिंग टूल्स को देखें।
OpenResty systemtap-आधारित एक्सटेंशन प्रदान करता है जो OpenResty प्रोग्राम्स का लाइव डायनामिक विश्लेषण करता है। आप इस प्रोजेक्ट के टूलसेट में कीवर्ड gc खोज सकते हैं, और आपको दो टूल्स, lj-gc और lj-gc-objs दिखाई देंगे।
core dump जैसे ऑफ़लाइन विश्लेषण के लिए, OpenResty एक GDB टूलसेट प्रदान करता है, और आप इसमें gc खोज सकते हैं और तीन टूल्स lgc, lgcstat और lgcpath पा सकते हैं।
इन डिबगिंग टूल्स का विशिष्ट उपयोग बाद में डिबगिंग अनुभाग में विस्तार से कवर किया जाएगा ताकि आप पहले एक प्रभाव प्राप्त कर सकें। आखिरकार, OpenResty के पास इन समस्याओं को स्थानांतरित करने और हल करने में आपकी मदद करने के लिए एक समर्पित टूलसेट है।
lua-resty-core
उपरोक्त तुलना से, हम देख सकते हैं कि FFI दृष्टिकोण न केवल कोड में साफ है, बल्कि LuaJIT द्वारा ऑप्टिमाइज़ भी किया जा सकता है, जो बेहतर विकल्प है। OpenResty ने CFunction कार्यान्वयन को अप्रचलित कर दिया है, और प्रदर्शन को कोडबेस से हटा दिया गया है। नए API अब FFI के माध्यम से lua-resty-core रिपॉजिटरी में लागू किए गए हैं।
मई 2019 में OpenResty के 1.15.8.1 रिलीज़ होने से पहले, lua-resty-core डिफ़ॉल्ट रूप से सक्षम नहीं था, जिसके कारण प्रदर्शन हानि और संभावित बग्स हुए, इसलिए मैं दृढ़ता से सलाह देता हूं कि कोई भी अभी भी ऐतिहासिक संस्करण का उपयोग कर रहा हो, मैन्युअल रूप से lua-resty-core को सक्षम करें। आपको केवल init_by_lua चरण में एक लाइन कोड जोड़ने की आवश्यकता है।
require "resty.core"
निश्चित रूप से, 1.15.8.1 रिलीज़ में lua_load_resty_core निर्देश जोड़ा गया है, और lua-resty-core डिफ़ॉल्ट रूप से सक्षम है।
मैं व्यक्तिगत रूप से महसूस करता हूं कि OpenResty lua-resty-core को सक्षम करने के बारे में अभी भी बहुत सतर्क है, और ओपन सोर्स प्रोजेक्ट्स को इस तरह की सुविधाओं को जल्द से जल्द डिफ़ॉल्ट रूप से सक्षम करना चाहिए।
lua-resty-core न केवल lua-nginx-module प्रोजेक्ट से कुछ API को पुनः लागू करता है, जैसे ngx.re.match, ngx.md5, आदि, बल्कि कई नए API भी लागू करता है, जैसे ngx.ssl, ngx.base64, ngx.errlog, ngx.process, ngx.re.process, और ngx.ngx.md5। ngx.re.split, ngx.resp.add_header, ngx.balancer, ngx.semaphore, आदि, जिन्हें हम बाद में OpenResty API अध्याय में कवर करेंगे।
सारांश
यह सब कहने के बाद, मैं यह निष्कर्ष निकालना चाहूंगा कि FFI, हालांकि अच्छा है, प्रदर्शन का चांदी का गोली नहीं है। यह मुख्य रूप से कुशल है क्योंकि इसे JIT द्वारा ट्रैक और ऑप्टिमाइज़ किया जा सकता है। यदि आप Lua कोड लिखते हैं जिसे JIT'd नहीं किया जा सकता है और इंटरप्रेटेड मोड में निष्पादित करने की आवश्यकता है, तो FFI कम कुशल होगा।
तो कौन से ऑपरेशन JIT किए जा सकते हैं और कौन से नहीं? हम कैसे कोड लिखने से बच सकते हैं जिसे JIT नहीं किया जा सकता है? मैं इसे अगले भाग में प्रकट करूंगा।
अंत में, एक हाथों-हाथ होमवर्क समस्या: क्या आप lua-nginx-module और lua-resty-core दोनों में एक या दो API ढूंढ सकते हैं, और फिर प्रदर्शन परीक्षणों में अंतर की तुलना कर सकते हैं? आप देख सकते हैं कि FFI का प्रदर्शन सुधार कितना महत्वपूर्ण है।
टिप्पणी करने के लिए आपका स्वागत है, और मैं आपके विचार और प्राप्तियों को साझा करूंगा और आपको इस लेख को अपने सहयोगियों और दोस्तों के साथ साझा करने के लिए आमंत्रित करता हूं, साथ में आदान-प्रदान और प्रगति।