---
title: "В чем разница между LuaJIT и стандартным Lua? - API7.ai"
description: "LuaJIT — это Just-In-Time Compiler (JIT) для языка программирования Lua. Что делает LuaJIT быстрее, чем Lua? В чем разница между ними?"
url: "https://api7.ai/ru/learning-center/openresty/luajit-vs-lua"
---

# В чем разница между LuaJIT и стандартным Lua?

API7.ai

September 23, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

Давайте узнаем о LuaJIT, еще одном краеугольном камне OpenResty, и я оставлю центральную часть сегодняшнего поста для некоторых важных и менее известных аспектов Lua и LuaJIT.

Вы можете узнать больше об основах Lua через поисковые системы или книги по Lua, и я рекомендую книгу *Programming in Lua* автора Lua.

Конечно, порог для написания правильного кода на LuaJIT в OpenResty не высок. Однако написание эффективного кода на LuaJIT — это не просто, и я подробно расскажу об этом в разделе оптимизации производительности OpenResty позже.

Давайте посмотрим, где LuaJIT находится в общей архитектуре OpenResty.

![Архитектура OpenResty](https://static.api7.ai/2022/09/23/632d2c8fa9d72.png?imageMogr2/format/webp)

Как упоминалось ранее, процессы `Worker` в OpenResty создаются путем форка процесса `Master`. Виртуальная машина LuaJIT в процессе `Master` также форкается. Все процессы `Worker` внутри одного `Worker` используют эту виртуальную машину LuaJIT, и выполнение кода Lua происходит в этой виртуальной машине.

Это основы работы OpenResty, которые мы обсудим более подробно в последующих статьях. Сегодня мы начнем с того, что разберем отношения между Lua и LuaJIT.

## Отношения между стандартным Lua и LuaJIT

Сначала разберем основные моменты.

Стандартный Lua и LuaJIT — это две разные вещи. LuaJIT совместим только с синтаксисом 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](https://github.com/openresty/luajit2).

Эти уникальные API были добавлены в ходе реальной разработки OpenResty по соображениям производительности. Таким образом, LuaJIT, о котором мы говорим далее, относится к ветке LuaJIT, поддерживаемой самим OpenResty.

## Почему LuaJIT?

После всего этого разговора об отношениях между LuaJIT и Lua, вы можете задаться вопросом, почему не использовать Lua напрямую, а использовать LuaJIT. На самом деле, основная причина — это преимущество в производительности LuaJIT.

Код Lua не интерпретируется напрямую, а компилируется в `Byte Code` компилятором Lua, а затем выполняется виртуальной машиной Lua.

Среда выполнения LuaJIT, помимо ассемблерной реализации интерпретатора Lua, имеет JIT-компилятор, который может генерировать машинный код напрямую. Сначала LuaJIT запускается как стандартный Lua, с кодом Lua, скомпилированным в байт-код, который интерпретируется и выполняется интерпретатором LuaJIT.

Разница в том, что интерпретатор LuaJIT записывает некоторые статистические данные во время выполнения байт-кода, такие как фактическое количество запусков каждой точки входа функции Lua и фактическое количество выполнений каждого цикла Lua. Когда эти счетчики превышают случайный порог, соответствующая точка входа функции Lua или цикл Lua считается достаточно "горячим", чтобы запустить JIT-компилятор.

JIT-компилятор пытается скомпилировать соответствующий путь кода Lua, начиная с точки входа "горячей" функции или места "горячего" цикла. Процесс компиляции преобразует байт-код LuaJIT в IR (Intermediate Representation), определенный 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`.

```
local s = 'hello'
```

Это связано с тем, что в Lua переменные по умолчанию являются глобальными и помещаются в таблицу с именем `_G`. Переменные, которые не являются локальными, ищутся в глобальной таблице, что является дорогостоящей операцией. Ошибки в написании имен переменных могут привести к ошибкам, которые трудно идентифицировать и исправить.

Поэтому в OpenResty я настоятельно рекомендую всегда объявлять переменные с помощью `local`, даже при подключении модуля.

```
-- Рекомендуется
local xxx = require('xxx')

-- Избегайте
require('xxx')
```

## LuaJIT

С учетом этих четырех особенностей Lua, перейдем к LuaJIT.

LuaJIT, помимо совместимости с Lua 5.1 и поддержки JIT, тесно интегрирован с FFI (Foreign Function Interface), что позволяет вам вызывать внешние функции 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? Поделитесь своими мнениями с нами, и я написал пост, чтобы поделиться своими ошибками. Вы также можете поделиться этим постом с коллегами и друзьями, чтобы учиться и развиваться вместе.

Share article link

Share

Related Posts

[![OpenResty FAQ | Как OpenResty используется на практике](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [OpenResty FAQ | Как OpenResty используется на практике](https://api7.ai/learning-center/openresty/faq-5)

[![Часть 3: Как создать API-шлюз для микросервисов с использованием OpenResty](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [Часть 3: Как создать API-шлюз для микросервисов с использованием OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [Отношения между стандартным Lua и LuaJIT](#%D0%BE%D1%82%D0%BD%D0%BE%D1%88%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BC%D0%B5%D0%B6%D0%B4%D1%83-%D1%81%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BD%D1%8B%D0%BC-lua-%D0%B8-luajit)

- [Почему LuaJIT?](#%D0%BF%D0%BE%D1%87%D0%B5%D0%BC%D1%83-luajit)

- [Особенности Lua](#%D0%BE%D1%81%D0%BE%D0%B1%D0%B5%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D0%B8-lua)

  - [1. Индексация начинается с 1](#1-%D0%B8%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D0%B0%D1%86%D0%B8%D1%8F-%D0%BD%D0%B0%D1%87%D0%B8%D0%BD%D0%B0%D0%B5%D1%82%D1%81%D1%8F-%D1%81-1)
  - [2. Использование `..` для конкатенации строк](#2-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5--%D0%B4%D0%BB%D1%8F-%D0%BA%D0%BE%D0%BD%D0%BA%D0%B0%D1%82%D0%B5%D0%BD%D0%B0%D1%86%D0%B8%D0%B8-%D1%81%D1%82%D1%80%D0%BE%D0%BA)
  - [3. Таблица — единственная структура данных](#3-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0--%D0%B5%D0%B4%D0%B8%D0%BD%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F-%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85)
  - [4. Все переменные по умолчанию глобальные](#4-%D0%B2%D1%81%D0%B5-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%BF%D0%BE-%D1%83%D0%BC%D0%BE%D0%BB%D1%87%D0%B0%D0%BD%D0%B8%D1%8E-%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5)

- [LuaJIT](#luajit)

- [Итог](#%D0%B8%D1%82%D0%BE%D0%B3)
