Qual é a diferença entre LuaJIT e Lua padrão?
API7.ai
September 23, 2022
Vamos aprender sobre o LuaJIT, outro pilar fundamental do OpenResty, e vou deixar a parte central do post de hoje para alguns aspectos essenciais e menos conhecidos do Lua e do LuaJIT.
Você pode aprender mais sobre o básico do Lua através de mecanismos de busca ou livros de Lua, e eu recomendo o livro Programming in Lua do autor do Lua.
Claro, o limiar para escrever o código LuaJIT correto no OpenResty não é alto. No entanto, não é fácil escrever código LuaJIT eficiente, e vou cobrir os elementos-chave aqui em detalhes na seção de otimização de desempenho do OpenResty mais tarde.
Vamos ver onde o LuaJIT se encaixa na arquitetura geral do OpenResty.
Como mencionado anteriormente, os processos Worker
do OpenResty são obtidos através do fork do processo Master
. A máquina virtual LuaJIT no processo Master
também é bifurcada. Todos os processos Worker
dentro do mesmo Worker
compartilham essa máquina virtual LuaJIT, e a execução do código Lua é feita nessa máquina virtual.
Esses são os fundamentos de como o OpenResty funciona, que discutiremos com mais detalhes em artigos subsequentes. Hoje vamos começar esclarecendo a relação entre Lua e LuaJIT.
Relação entre Lua Padrão e LuaJIT
Vamos começar com o essencial.
Lua padrão e LuaJIT são duas coisas diferentes. LuaJIT é compatível apenas com a sintaxe do Lua 5.1.
A versão mais recente do Lua padrão é a 5.4.4, e a versão mais recente do LuaJIT é a 2.1.0-beta3. Em versões mais antigas do OpenResty de alguns anos atrás, você podia escolher usar a máquina virtual Lua padrão ou a máquina virtual LuaJIT como ambiente de execução ao compilar, mas agora o suporte ao Lua padrão foi removido, e apenas o LuaJIT é suportado.
A sintaxe do LuaJIT é compatível com o Lua 5.1, com suporte opcional para Lua 5.2 e 5.3. Portanto, devemos primeiro aprender a sintaxe do Lua 5.1 e, com base nisso, aprender os recursos do LuaJIT. No artigo anterior, eu mostrei a sintaxe básica do Lua. Hoje vou mencionar apenas alguns recursos únicos do Lua.
Vale ressaltar que o OpenResty não usa diretamente a versão oficial do LuaJIT 2.1.0-beta3, mas a estende com seu fork: openresty-luajit2.
Essas APIs únicas foram adicionadas durante o desenvolvimento real do OpenResty por razões de desempenho. Portanto, o LuaJIT que mencionamos mais tarde se refere ao branch do LuaJIT mantido pelo próprio OpenResty.
Por que LuaJIT?
Depois de toda essa conversa sobre a relação entre LuaJIT e Lua, você pode se perguntar por que não usar o Lua diretamente, mas usar o LuaJIT. Na verdade, a principal razão é a vantagem de desempenho do LuaJIT.
O código Lua não é interpretado diretamente, mas compilado em Byte Code
pelo compilador Lua e, em seguida, executado pela máquina virtual Lua.
O ambiente de execução do LuaJIT, além de uma implementação em assembly do interpretador Lua, possui um compilador JIT que pode gerar código de máquina diretamente. No início, o LuaJIT começa como o Lua padrão, com o código Lua compilado para bytecode, que é interpretado e executado pelo interpretador do LuaJIT.
A diferença é que o interpretador do LuaJIT registra algumas estatísticas de tempo de execução enquanto executa o bytecode, como o número real de vezes que cada entrada de função Lua é executada e o número real de vezes que cada loop Lua é executado. Quando essas contagens ultrapassam um limite aleatório, a entrada da função Lua ou o loop Lua correspondente é considerado quente o suficiente para acionar o compilador JIT.
O compilador JIT tenta compilar o caminho do código Lua correspondente, começando pela entrada da função quente ou pela localização do loop quente. O processo de compilação converte o bytecode do LuaJIT em IR (Representação Intermediária) definida pelo próprio LuaJIT e, em seguida, gera código de máquina para a arquitetura de destino.
Portanto, a chamada otimização de desempenho do LuaJIT é essencialmente sobre fazer com que o máximo de código Lua possível seja gerado como código de máquina pelo compilador JIT, em vez de cair no modo de execução interpretado do interpretador Lua. Uma vez que você entende isso, pode entender a natureza da otimização de desempenho do OpenResty que você aprenderá mais tarde.
Recursos especiais do Lua
Conforme descrito no artigo anterior, a linguagem Lua é relativamente simples. Para engenheiros com experiência em outras linguagens de desenvolvimento, é fácil entender a lógica do código uma vez que você percebe alguns aspectos únicos do Lua. A seguir, vamos ver alguns dos aspectos mais incomuns da linguagem Lua.
1. Índice começa em 1
Lua é a única linguagem de programação que eu conheço que começa com um índice de 1
. Isso, embora seja mais fácil de entender para pessoas sem formação em programação, é propenso a bugs. Aqui está um exemplo.
$ resty -e 't={100}; ngx.say(t[0])'
Você pode esperar que o programa imprima 100
ou que reporte um erro dizendo que o índice 0
não existe. Mas, surpreendentemente, nada é impresso, e nenhum erro é reportado. Então, vamos adicionar o comando type
e ver qual é a saída.
$ resty -e 't={100};ngx.say(type(t[0]))'
nil
Acontece que é o valor nil
. Na verdade, no OpenResty, a determinação e o tratamento de valores nil
também são um ponto confuso, então falaremos mais sobre isso mais tarde, quando falarmos sobre o OpenResty.
2. Use ..
para concatenar strings
Ao contrário da maioria das linguagens que usam +
, o Lua usa dois pontos para concatenar strings.
$ resty -e "ngx.say('hello' .. ', world')"
hello, world
No desenvolvimento real de projetos, geralmente usamos várias linguagens de desenvolvimento, e o design incomum do Lua sempre faz os desenvolvedores pensarem quando a concatenação de strings fica um pouco confusa.
3. A tabela é a única estrutura de dados
Ao contrário do Python, uma linguagem rica em estruturas de dados embutidas, o Lua tem apenas uma estrutura de dados, a tabela
, que pode incluir arrays e tabelas hash.
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
Se você não atribuir explicitamente um valor como um par chave-valor, a tabela assume um número como índice, começando com 1
. Portanto, color[1]
é blue
.
Além disso, obter o comprimento correto na tabela é difícil, então vamos ver esses exemplos.
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))
Resultado:
Test1 3
Test2 2
Test3 1
Test4
Como você pode ver, exceto pelo primeiro caso de teste que retorna um comprimento de 3
, os testes subsequentes estão todos fora de nossas expectativas. Na verdade, para obter o comprimento da tabela em Lua, é importante notar que o valor correto é retornado apenas se a tabela for uma sequência.
Então, o que é uma sequência? Primeiro, uma sequência é um subconjunto de um array. Ou seja, os elementos de uma tabela são acessíveis com um índice de número inteiro positivo e não há pares chave-valor. No código acima, todas as tabelas são arrays, exceto t2
.
Em segundo lugar, a sequência não contém buracos, ou seja, nil
. Combinando esses dois pontos, a tabela t1
acima é uma sequência, enquanto t3
e t4
são arrays, mas não sequências.
Até este ponto, você ainda pode ter uma dúvida: por que o comprimento de t4
será 1
? Isso ocorre porque, quando nil
é encontrado, a lógica para obter o comprimento não continua a ser executada, mas retorna diretamente.
Eu não sei se você entendeu completamente. Esta parte é realmente bastante complicada. Então, há alguma maneira de obter o comprimento da tabela que queremos? Naturalmente, há. O OpenResty estende isso, e vou falar sobre isso mais tarde no capítulo dedicado à tabela, então vamos deixar o suspense aqui.
4. Todas as variáveis são globais por padrão
Eu gostaria de enfatizar que, a menos que você tenha certeza, você deve sempre declarar novas variáveis como variáveis locais
.
local s = 'hello'
Isso porque, em Lua, as variáveis são globais por padrão e são colocadas em uma tabela chamada _G
. Variáveis que não são locais são procuradas na tabela global, o que é uma operação cara. Erros de digitação em nomes de variáveis podem levar a bugs difíceis de identificar e corrigir.
Portanto, no OpenResty, eu recomendo fortemente que você sempre declare variáveis usando local
, mesmo quando você requer um módulo.
-- Recomendado
local xxx = require('xxx')
-- Evite
require('xxx')
LuaJIT
Com esses quatro recursos especiais do Lua em mente, vamos passar para o LuaJIT.
O LuaJIT, além de ser compatível com o Lua 5.1 e suportar JIT, está intimamente integrado com o FFI (Interface de Função Estrangeira), permitindo que você chame funções C externas e use estruturas de dados C diretamente em seu código Lua. Aqui está o exemplo mais simples.
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!", "world")
Em apenas algumas linhas de código, você pode chamar a função printf
do C diretamente do Lua e imprimir Hello world!
Você pode usar o comando resty
para executá-lo e ver se funciona.
Da mesma forma, podemos usar o FFI para chamar as funções C do NGINX e do OpenSSL para fazer muito mais. A abordagem FFI tem um desempenho melhor do que a abordagem tradicional da API Lua/C, e é por isso que o projeto lua-resty-core
existe. Na próxima seção, falaremos sobre FFI e lua-resty-core
.
Além disso, por razões de desempenho, o LuaJIT estende as funções da tabela: table.new
e table.clear
, duas funções essenciais de otimização de desempenho frequentemente usadas na biblioteca lua-resty
do OpenResty. No entanto, poucos desenvolvedores estão familiarizados com elas, pois a documentação é intensa e não há código de exemplo. Vamos guardá-las para a seção de otimização de desempenho.
Resumo
Vamos revisar o conteúdo de hoje.
O OpenResty escolhe o LuaJIT em vez do Lua padrão por razões de desempenho e mantém seu próprio branch do LuaJIT. O LuaJIT é baseado na sintaxe do Lua 5.1 e é seletivamente compatível com algumas sintaxes do Lua 5.2 e Lua 5.3 para formar seu sistema. Quanto à sintaxe do Lua que você precisa dominar, ela tem suas características distintas em índice, concatenação de strings, estruturas de dados e variáveis, às quais você deve prestar atenção especial ao escrever código.
Você já encontrou alguma armadilha ao aprender Lua e LuaJIT? Sinta-se à vontade para compartilhar suas opiniões conosco, e eu escrevi um post para compartilhar as armadilhas que encontrei. Você também é bem-vindo para compartilhar este post com seus colegas e amigos para aprender e progredir juntos.