ما الفرق بين LuaJIT و Lua القياسي؟
API7.ai
September 23, 2022
لنتعلم عن LuaJIT، وهو حجر زاوية آخر من OpenResty، وسأترك الجزء الرئيسي من منشور اليوم لبعض الجوانب الأساسية والأقل شهرة في Lua وLuaJIT.
يمكنك تعلم الأساسيات عن Lua من خلال محركات البحث أو كتب Lua، وأوصي بكتاب Programming in Lua لمؤلف Lua.
بالطبع، عتبة كتابة كود LuaJIT صحيح في OpenResty ليست عالية. ومع ذلك، فإن كتابة كود LuaJIT فعال ليس بالأمر السهل، وسأغطي العناصر الرئيسية هنا بالتفصيل في قسم تحسين أداء OpenResty لاحقًا.
لننظر إلى مكان LuaJIT في الهيكل العام لـ OpenResty.
كما ذكرنا سابقًا، يتم الحصول على عمليات Worker
في OpenResty من خلال تفرع عملية Master
. يتم أيضًا تفرع آلة LuaJIT الافتراضية في عملية Master
. جميع عمليات Worker
داخل نفس Worker
تشارك هذه الآلة الافتراضية LuaJIT، ويتم تنفيذ كود Lua في هذه الآلة الافتراضية.
هذه هي الأساسيات حول كيفية عمل OpenResty، والتي سنناقشها بمزيد من التفصيل في المقالات اللاحقة. اليوم سنبدأ بفهم العلاقة بين Lua وLuaJIT.
العلاقة بين Lua القياسي وLuaJIT
لنبدأ بالأشياء الأساسية أولاً.
Lua القياسي وLuaJIT هما شيئان مختلفان. LuaJيتوافق فقط مع صيغة Lua 5.1.
أحدث إصدار من Lua القياسي هو الآن 5.4.4، وأحدث إصدار من LuaJIT هو 2.1.0-beta3. في الإصدارات القديمة من OpenResty منذ بضع سنوات، كان يمكنك اختيار استخدام إما آلة Lua القياسية أو آلة LuaJIT كبيئة تنفيذ عند التجميع، ولكن الآن تمت إزالة الدعم لـ Lua القياسي، ويتم دعم LuaJIT فقط.
صيغة LuaJIT متوافقة مع Lua 5.1، مع دعم اختياري لبعض صيغ Lua 5.2 و5.3. لذا يجب أن نتعلم أولاً صيغة Lua 5.1 ونبني عليها لتعلم ميزات LuaJIT. في المقالة السابقة، أخذتك إلى الصيغة الأساسية لـ Lua. اليوم سأذكر فقط بعض الميزات الفريدة لـ Lua.
من الجدير بالذكر أن OpenResty لا تستخدم الإصدار الرسمي من LuaJIT 2.1.0-beta3 مباشرة، بل تقوم بتوسيعه باستخدام نسختها الخاصة: openresty-luajit2.
تمت إضافة هذه الواجهات البرمجية الفريدة أثناء التطوير الفعلي لـ OpenResty لأسباب تتعلق بالأداء. لذا، فإن LuaJIT الذي نذكره لاحقًا يشير إلى الفرع الذي تحتفظ به OpenResty بنفسها.
لماذا LuaJIT؟
بعد كل هذا الحديث عن العلاقة بين LuaJIT وLua، قد تتساءل لماذا لا نستخدم Lua مباشرة، بل نستخدم LuaJIT. في الواقع، السبب الرئيسي هو الميزة الأدائية لـ LuaJIT.
لا يتم تفسير كود Lua مباشرة، بل يتم تجميعه إلى Byte Code
بواسطة مترجم Lua ثم يتم تنفيذه بواسطة الآلة الافتراضية لـ Lua.
بيئة تنفيذ LuaJIT، بالإضافة إلى تنفيذ تجميعي لمترجم Lua، تحتوي على مترجم JIT يمكنه توليد كود الآلة مباشرة. في البداية، يبدأ LuaJIT مثل Lua القياسي، حيث يتم تجميع كود Lua إلى byte code، والذي يتم تفسيره وتنفيذه بواسطة مترجم LuaJIT.
الفرق هو أن مترجم LuaJIT يسجل بعض الإحصائيات أثناء تنفيذ bytecode، مثل عدد المرات الفعلي التي يتم فيها تشغيل كل مدخل استدعاء دالة Lua وعدد المرات الفعلي التي يتم فيها تنفيذ كل حلقة Lua. عندما تتجاوز هذه الأعداد عتبة عشوائية، يتم اعتبار مدخل الدالة أو الحلقة ساخنًا بما يكفي لبدء عمل مترجم JIT.
يحاول مترجم JIT تجميع مسار كود Lua المقابل، بدءًا من مدخل الدالة الساخنة أو موقع الحلقة الساخنة. تقوم عملية التجميع بتحويل bytecode LuaJIT إلى IR (تمثيل وسيط) محدد بواسطة LuaJIT ثم توليد كود الآلة للهندسة المستهدفة.
لذا، فإن تحسين أداء LuaJIT هو في الأساس جعل أكبر قدر ممكن من كود Lua متاحًا لتوليد كود الآلة بواسطة مترجم JIT، بدلاً من العودة إلى وضع التنفيذ التفسيري لمترجم Lua. بمجرد فهمك لهذا، يمكنك فهم جوهر تحسين أداء OpenResty الذي ستتعلمه لاحقًا.
الميزات الخاصة لـ Lua
كما ورد في المقالة السابقة، لغة Lua بسيطة نسبيًا. بالنسبة للمهندسين الذين لديهم خلفية في لغات تطوير أخرى، من السهل فهم منطق الكود بمجرد ملاحظة بعض الجوانب الفريدة لـ Lua. بعد ذلك، دعونا ننظر إلى بعض الجوانب الأكثر غرابة في لغة Lua.
1. الفهرس يبدأ من 1
Lua هي اللغة البرمجية الوحيدة التي أعرفها والتي تبدأ الفهرس من 1
. هذا، بينما يكون مفهومًا بشكل أفضل من قبل غير المبرمجين، إلا أنه عرضة لأخطاء البرمجة. هنا مثال.
$ resty -e 't={100}; ngx.say(t[0])'
قد تتوقع أن يطبع البرنامج 100
أو أن يبلغ عن خطأ يقول أن الفهرس 0
غير موجود. ولكن المفاجأة هي أنه لا يتم طباعة أي شيء، ولا يتم الإبلاغ عن أي أخطاء. لذا دعونا نضيف الأمر type
ونرى ما هو الناتج.
$ resty -e 't={100};ngx.say(type(t[0]))'
nil
اتضح أنه قيمة nil
. في الواقع، في OpenResty، تحديد ومعالجة قيم nil
هو أيضًا نقطة مربكة، لذا سنتحدث عنها أكثر لاحقًا عندما نتحدث عن OpenResty.
2. استخدام ..
لربط السلاسل النصية
على عكس معظم اللغات التي تستخدم +
، تستخدم Lua نقطتين لربط السلاسل النصية.
$ resty -e "ngx.say('hello' .. ', world')"
hello, world
في تطوير المشاريع الفعلية، نستخدم عادةً عدة لغات تطوير، وسيؤدي التصميم غير المعتاد لـ Lua دائمًا إلى جعل المطورين يفكرون عندما يكون ربط السلاسل النصية مربكًا قليلاً.
3. الجدول هو الهيكل الوحيد للبيانات
على عكس Python، وهي لغة غنية بهياكل البيانات المدمجة، تحتوي Lua على هيكل بيانات واحد فقط، وهو table
، والذي يمكن أن يشمل المصفوفات وجداول التجزئة.
local color = {first = "red", "blue", third = "green", "yellow"}
print(color["first"]) --> output: red
print(color[1]) --> output: blue
print(color["third"]) --> output: green
print(color[2]) --> output: yellow
print(color[3]) --> output: nil
إذا لم تقم بتعيين قيمة بشكل صريح كـ زوج مفتاح-قيمة، فإن الجدول يستخدم رقمًا كفهرس بشكل افتراضي، بدءًا من 1
. لذا color[1]
هو blue
.
أيضًا، الحصول على الطول الصحيح في الجدول صعب، لذا دعونا ننظر إلى هذه الأمثلة.
local t1 = { 1, 2, 3 }
print("Test1 " .. table.getn(t1))
local t2 = { 1, a = 2, 3 }
print("Test2 " .. table.getn(t2))
local t3 = { 1, nil }
print("Test3 " .. table.getn(t3))
local t4 = { 1, nil, 2 }
print("Test4 " .. table.getn(t4))
النتيجة:
Test1 3
Test2 2
Test3 1
Test4
كما ترى، باستثناء حالة الاختبار الأولى التي تعيد طولًا 3
، فإن الاختبارات اللاحقة كلها خارج توقعاتنا. في الواقع، للحصول على طول الجدول في Lua، من المهم ملاحظة أن القيمة الصحيحة يتم إرجاعها فقط إذا كان الجدول تسلسلًا.
إذن ما هو التسلسل؟ أولاً، التسلسل هو مجموعة فرعية من المصفوفة. أي أن عناصر الجدول يمكن الوصول إليها باستخدام فهرس عدد صحيح موجب ولا توجد أزواج مفتاح-قيمة. في الكود أعلاه، جميع الجداول هي مصفوفات باستثناء t2
.
ثانيًا، التسلسل لا يحتوي على ثقوب، أي nil
. بدمج هاتين النقطتين، الجدول أعلاه، t1
، هو تسلسل، بينما t3
وt4
هما مصفوفات ولكن ليسا تسلسلات.
حتى هذه النقطة، قد لا يزال لديك سؤال، لماذا يكون طول t4
1
؟ هذا لأنه عند مواجهة nil
، لا تستمر منطقية الحصول على الطول في العمل بل تعود مباشرة.
لا أعرف إذا كنت قد فهمتها تمامًا. هذا الجزء معقد حقًا. لذا هل هناك أي طريقة للحصول على طول الجدول الذي نريده؟ بالطبع، هناك. قام OpenResty بتوسيع هذا، وسأتحدث عنه لاحقًا في الفصل المخصص للجدول، لذا دعونا نترك التشويق هنا.
4. جميع المتغيرات عامة بشكل افتراضي
أود التأكيد على أنه ما لم تكن متأكدًا تمامًا، يجب عليك دائمًا الإعلان عن المتغيرات الجديدة كمتغيرات محلية.
local s = 'hello'
هذا لأنه في Lua، المتغيرات عامة بشكل افتراضي ويتم وضعها في جدول يسمى _G
. يتم البحث عن المتغيرات التي ليست محلية في الجدول العام، وهي عملية مكلفة. يمكن أن يؤدي الخطأ في تهجئة أسماء المتغيرات إلى أخطاء يصعب تحديدها وإصلاحها.
لذا، في OpenResty، أوصي بشدة بأن تقوم دائمًا بالإعلان عن المتغيرات باستخدام local
، حتى عند طلب وحدة.
-- موصى به
local xxx = require('xxx')
-- تجنب
require('xxx')
LuaJIT
مع تذكر هذه الميزات الأربع الخاصة لـ Lua، دعنا ننتقل إلى LuaJIT.
LuaJIT، بالإضافة إلى كونه متوافقًا مع Lua 5.1 ويدعم JIT، يتم دمجه بشكل وثيق مع FFI (واجهة الوظائف الأجنبية)، مما يسمح لك باستدعاء وظائف C الخارجية واستخدام هياكل بيانات C مباشرة في كود Lua الخاص بك. هنا أبسط مثال.
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!", "world")
في بضعة أسطر من الكود، يمكنك استدعاء وظيفة printf
من C مباشرة من Lua وطباعة Hello world!
يمكنك استخدام الأمر resty
لتشغيله ومعرفة ما إذا كان يعمل.
بالمثل، يمكننا استخدام FFI لاستدعاء وظائف C الخاصة بـ NGINX وOpenSSL للقيام بالكثير. طريقة FFI تؤدي أداءً أفضل من طريقة Lua/C API التقليدية، وهذا هو سبب وجود مشروع lua-resty-core
. في القسم التالي، سنتحدث عن FFI وlua-resty-core
.
بالإضافة إلى ذلك، لأسباب تتعلق بالأداء، قام LuaJIT بتوسيع وظائف الجدول: table.new
وtable.clear
، وهما وظيفتان أساسيتان لتحسين الأداء تُستخدمان بشكل متكرر في مكتبة lua-resty
الخاصة بـ OpenResty. ومع ذلك، فإن عددًا قليلاً من المطورين على دراية بهما، حيث أن الوثائق مكثفة ولا يوجد كود نموذجي. سنتركها لقسم تحسين الأداء.
الخلاصة
دعونا نراجع محتوى اليوم.
يختار OpenResty LuaJIT بدلاً من Lua القياسي لأسباب تتعلق بالأداء ويحتفظ بفرع LuaJIT الخاص به. LuaJIT يعتمد على صيغة Lua 5.1 وهو متوافق بشكل اختياري مع بعض صيغ Lua 5.2 وLua 5.3 لتشكيل نظامه. أما بالنسبة للصيغة التي تحتاج إلى إتقانها في Lua، فإن لها ميزاتها الفريدة في الفهرس، ربط السلاسل النصية، هياكل البيانات، والمتغيرات، والتي يجب أن تنتبه إليها عند كتابة الكود.
هل واجهت أي صعوبات عند تعلم Lua وLuaJIT؟ لا تتردد في مشاركة آرائك معنا، وقد كتبت منشورًا لمشاركة الصعوبات التي واجهتها. أنت أيضًا مرحب بك لمشاركة هذا المنشور مع زملائك وأصدقائك للتعلم والتقدم معًا.