Was ist der Unterschied zwischen LuaJIT und Standard Lua?
API7.ai
September 23, 2022
Lassen Sie uns über LuaJIT lernen, einen weiteren Eckpfeiler von OpenResty, und ich werde den zentralen Teil des heutigen Beitrags einigen wesentlichen und weniger bekannten Aspekten von Lua und LuaJIT widmen.
Sie können die Grundlagen von Lua über Suchmaschinen oder Lua-Bücher lernen, und ich empfehle das Buch Programming in Lua vom Lua-Autor.
Natürlich ist die Schwelle, korrekten LuaJIT-Code in OpenResty zu schreiben, nicht hoch. Dennoch ist es nicht einfach, effizienten LuaJIT-Code zu schreiben, und ich werde die Schlüsselelemente hier später im Abschnitt zur OpenResty-Leistungsoptimierung ausführlich behandeln.
Lassen Sie uns zunächst betrachten, wo LuaJIT in die Gesamtarchitektur von OpenResty passt.
Wie bereits erwähnt, werden OpenResty-Worker
-Prozesse durch Forking des Master
-Prozesses erstellt. Die LuaJIT-Virtual-Machine im Master
-Prozess wird ebenfalls geforkt. Alle Worker
-Prozesse innerhalb desselben Worker
teilen sich diese LuaJIT-Virtual-Machine, und die Ausführung von Lua-Code erfolgt in dieser Virtual-Machine.
Dies sind die Grundlagen, wie OpenResty funktioniert, die wir in späteren Artikeln ausführlicher besprechen werden. Heute beginnen wir damit, die Beziehung zwischen Lua und LuaJIT zu klären.
Beziehung zwischen Standard Lua und LuaJIT
Lassen Sie uns zunächst die wesentlichen Dinge klären.
Standard Lua und LuaJIT sind zwei verschiedene Dinge. LuaJIT ist nur mit der Lua 5.1-Syntax kompatibel.
Die neueste Version von Standard Lua ist jetzt 5.4.4, und die neueste Version von LuaJIT ist 2.1.0-beta3. In älteren Versionen von OpenResty vor einigen Jahren konnte man beim Kompilieren entweder die Standard-Lua-VM oder die LuaJIT-VM als Ausführungsumgebung wählen, aber jetzt wurde die Unterstützung für Standard Lua entfernt, und nur LuaJIT wird unterstützt.
Die LuaJIT-Syntax ist mit Lua 5.1 kompatibel, mit optionaler Unterstützung für Lua 5.2 und 5.3. Daher sollten wir zunächst die Lua 5.1-Syntax lernen und darauf aufbauend die LuaJIT-Funktionen lernen. Im vorherigen Artikel habe ich Sie in die Grundlagen der Lua-Syntax eingeführt. Heute werde ich nur einige einzigartige Merkmale von Lua erwähnen.
Es ist erwähnenswert, dass OpenResty nicht direkt die offizielle LuaJIT-Version 2.1.0-beta3 verwendet, sondern sie mit seinem Fork erweitert: openresty-luajit2.
Diese einzigartigen APIs wurden während der tatsächlichen Entwicklung von OpenResty aus Leistungsgründen hinzugefügt. Daher bezieht sich das LuaJIT, das wir später erwähnen, auf den von OpenResty selbst gepflegten LuaJIT-Zweig.
Warum LuaJIT?
Nach all dem Gerede über die Beziehung zwischen LuaJIT und Lua fragen Sie sich vielleicht, warum man nicht direkt Lua verwendet, sondern LuaJIT. Tatsächlich ist der Hauptgrund der Leistungsvorteil von LuaJIT.
Lua-Code wird nicht direkt interpretiert, sondern vom Lua-Compiler in Byte Code
kompiliert und dann von der Lua-Virtual-Machine ausgeführt.
Die LuaJIT-Laufzeitumgebung verfügt neben einer Assembler-Implementierung des Lua-Interpreters über einen JIT-Compiler, der direkt Maschinencode erzeugen kann. Zunächst startet LuaJIT wie Standard Lua, wobei Lua-Code in Bytecode kompiliert wird, der vom LuaJIT-Interpreter interpretiert und ausgeführt wird.
Der Unterschied besteht darin, dass der LuaJIT-Interpreter während der Ausführung des Bytecodes einige Laufzeitstatistiken aufzeichnet, wie z. B. die tatsächliche Anzahl der Ausführungen jedes Lua-Funktionsaufrufs und die tatsächliche Anzahl der Ausführungen jeder Lua-Schleife. Wenn diese Zählungen einen zufälligen Schwellenwert überschreiten, wird der entsprechende Lua-Funktionseintrag oder die Lua-Schleife als heiß genug betrachtet, um den JIT-Compiler zu starten.
Der JIT-Compiler versucht, den entsprechenden Lua-Code-Pfad zu kompilieren, beginnend am Eintrittspunkt der heißen Funktion oder der Position der heißen Schleife. Der Kompilierungsprozess wandelt den LuaJIT-Bytecode in eine von LuaJIT definierte IR (Intermediate Representation) um und erzeugt dann Maschinencode für die Zielarchitektur.
Daher besteht die sogenannte LuaJIT-Leistungsoptimierung im Wesentlichen darin, so viel Lua-Code wie möglich für die Maschinencodegenerierung durch den JIT-Compiler verfügbar zu machen, anstatt in den Interpretationsmodus des Lua-Interpreters zurückzufallen. Sobald Sie dies verstanden haben, können Sie die Natur der OpenResty-Leistungsoptimierung verstehen, die Sie später lernen werden.
Besondere Merkmale von Lua
Wie im vorherigen Artikel beschrieben, ist die Lua-Sprache relativ einfach. Für Ingenieure mit einem Hintergrund in anderen Entwicklungssprachen ist es leicht, die Logik des Codes zu erkennen, sobald Sie einige einzigartige Aspekte von Lua bemerken. Als nächstes betrachten wir einige der ungewöhnlicheren Aspekte der Lua-Sprache.
1. Index beginnt bei 1
Lua ist die einzige Programmiersprache, die ich kenne, die mit einem Index von 1
beginnt. Dies ist zwar für Nicht-Programmierer besser verständlich, führt jedoch leicht zu Programmierfehlern. Hier ist ein Beispiel.
$ resty -e 't={100}; ngx.say(t[0])'
Sie könnten erwarten, dass das Programm 100
ausgibt oder einen Fehler meldet, dass der Index 0
nicht existiert. Aber überraschenderweise wird nichts ausgegeben, und es werden keine Fehler gemeldet. Fügen wir also den type
-Befehl hinzu und sehen, was die Ausgabe ist.
$ resty -e 't={100};ngx.say(type(t[0]))'
nil
Es stellt sich heraus, dass es sich um den nil
-Wert handelt. Tatsächlich ist die Bestimmung und Handhabung von nil
-Werten in OpenResty ebenfalls ein verwirrender Punkt, daher werden wir später mehr darüber sprechen, wenn wir über OpenResty sprechen.
2. Verwenden von ..
zum Verketten von Zeichenketten
Im Gegensatz zu den meisten Sprachen, die +
verwenden, verwendet Lua zwei Punkte, um Zeichenketten zu verketten.
$ resty -e "ngx.say('hello' .. ', world')"
hello, world
In der tatsächlichen Projektentwicklung verwenden wir in der Regel mehrere Entwicklungssprachen, und das ungewöhnliche Design von Lua wird Entwickler immer wieder zum Nachdenken bringen, wenn die Zeichenkettenverknüpfung ein wenig verwirrend ist.
3. Die Tabelle ist die einzige Datenstruktur
Im Gegensatz zu Python, einer Sprache mit vielen eingebauten Datenstrukturen, hat Lua nur eine Datenstruktur, die Tabelle
, die Arrays und Hash-Tabellen umfassen kann.
local color = {first = "red", "blue", third = "green", "yellow"}
print(color["first"]) --> Ausgabe: red
print(color[1]) --> Ausgabe: blue
print(color["third"]) --> Ausgabe: green
print(color[2]) --> Ausgabe: yellow
print(color[3]) --> Ausgabe: nil
Wenn Sie keinen Wert explizit als Schlüssel-Wert-Paar zuweisen, verwendet die Tabelle standardmäßig eine Zahl als Index, beginnend mit 1
. Daher ist color[1]
blue
.
Außerdem ist es schwierig, die korrekte Länge in der Tabelle zu erhalten. Schauen wir uns diese Beispiele an.
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))
Ergebnis:
Test1 3
Test2 2
Test3 1
Test4
Wie Sie sehen können, gibt außer dem ersten Testfall, der eine Länge von 3
zurückgibt, keiner der nachfolgenden Tests das erwartete Ergebnis zurück. Tatsächlich ist es wichtig zu beachten, dass in Lua nur dann die korrekte Länge der Tabelle zurückgegeben wird, wenn die Tabelle eine Sequenz ist.
Was ist also eine Sequenz? Zunächst ist eine Sequenz eine Teilmenge eines Arrays. Das heißt, die Elemente einer Tabelle sind mit einem positiven ganzzahligen Index zugänglich, und es gibt keine Schlüssel-Wert-Paare. Im obigen Code sind alle Tabellen außer t2
Arrays.
Zweitens enthält die Sequenz keine Lücken, d. h. nil
. Kombiniert man diese beiden Punkte, ist die obige Tabelle t1
eine Sequenz, während t3
und t4
Arrays, aber keine Sequenzen sind.
Bis zu diesem Punkt haben Sie vielleicht immer noch eine Frage, warum die Länge von t4
1
ist? Dies liegt daran, dass bei nil
die Logik zur Ermittlung der Länge nicht weiterläuft, sondern direkt zurückgegeben wird.
Ich weiß nicht, ob Sie es vollständig verstanden haben. Dieser Teil ist wirklich ziemlich kompliziert. Gibt es also eine Möglichkeit, die gewünschte Tabellenlänge zu erhalten? Natürlich gibt es das. OpenResty erweitert dies, und ich werde später im Kapitel über die Tabelle darauf eingehen, lassen wir also die Spannung hier.
4. Alle Variablen sind standardmäßig global
Ich möchte betonen, dass Sie, es sei denn, Sie sind sich ziemlich sicher, neue Variablen immer als local
-Variablen deklarieren sollten.
local s = 'hello'
Dies liegt daran, dass in Lua Variablen standardmäßig global sind und in einer Tabelle namens _G
platziert werden. Variablen, die nicht lokal sind, werden in der globalen Tabelle gesucht, was ein teurer Vorgang ist. Falsch geschriebene Variablennamen können zu Fehlern führen, die schwer zu identifizieren und zu beheben sind.
Daher empfehle ich in OpenResty dringend, dass Sie Variablen immer mit local
deklarieren, auch wenn Sie ein Modul benötigen.
-- Empfohlen
local xxx = require('xxx')
-- Vermeiden
require('xxx')
LuaJIT
Mit diesen vier besonderen Merkmalen von Lua im Hinterkopf, kommen wir nun zu LuaJIT.
LuaJIT ist neben der Lua 5.1-Kompatibilität und der JIT-Unterstützung eng mit der FFI (Foreign Function Interface) integriert, was es Ihnen ermöglicht, externe C-Funktionen aufzurufen und C-Datenstrukturen direkt in Ihrem Lua-Code zu verwenden. Hier ist das einfachste Beispiel.
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!", "world")
In nur wenigen Codezeilen können Sie die C-Funktion printf
direkt aus Lua aufrufen und Hello world!
ausgeben. Sie können den resty
-Befehl verwenden, um es auszuführen und zu sehen, ob es funktioniert.
Ebenso können wir FFI verwenden, um die C-Funktionen von NGINX und OpenSSL aufzurufen, um viel mehr zu tun. Der FFI-Ansatz bietet eine bessere Leistung als der traditionelle Lua/C-API-Ansatz, weshalb das lua-resty-core
-Projekt existiert. Im nächsten Abschnitt werden wir über FFI und lua-resty-core
sprechen.
Darüber hinaus erweitert LuaJIT aus Leistungsgründen die Tabellenfunktionen: table.new
und table.clear
, zwei wesentliche Leistungsoptimierungsfunktionen, die häufig in der lua-resty
-Bibliothek von OpenResty verwendet werden. Allerdings sind nur wenige Entwickler mit ihnen vertraut, da die Dokumentation intensiv ist und es keinen Beispielcode gibt. Wir werden sie für den Abschnitt zur Leistungsoptimierung aufheben.
Zusammenfassung
Lassen Sie uns den heutigen Inhalt überprüfen.
OpenResty wählt LuaJIT anstelle von Standard Lua aus Leistungsgründen und pflegt seinen eigenen LuaJIT-Zweig. LuaJIT basiert auf der Lua 5.1-Syntax und ist selektiv mit einigen Lua 5.2- und Lua 5.3-Syntaxen kompatibel, um sein System zu bilden. Was die Lua-Syntax betrifft, die Sie beherrschen müssen, hat sie ihre besonderen Merkmale in Bezug auf Index, Zeichenkettenverknüpfung, Datenstrukturen und Variablen, auf die Sie beim Schreiben von Code besonders achten sollten.
Sind Ihnen beim Lernen von Lua und LuaJIT Fallstricke begegnet? Teilen Sie uns Ihre Meinungen mit, und ich habe einen Beitrag geschrieben, um die Fallstricke zu teilen, auf die ich gestoßen bin. Sie sind auch eingeladen, diesen Beitrag mit Ihren Kollegen und Freunden zu teilen, um gemeinsam zu lernen und Fortschritte zu machen.