Switch lang: Русский \ English

Почему мы выбрали LUA, сюрпризы луа


Что такое LUA, почему мы выбрали LUA

общий обзор языка и альтернатив ему в контексте нашей задачи

Сюрпризы языка lua, или вещи которые удивили, но поздно

список странностей обнаруженных в процессе работу над системой ;)

LUA оказался очень странным языком! ОЧЕНЬ! Я когда на нём писал не задумывался об этих пробламах совершенно! А тут нам пришлось иметь дело с его внутренностями, и мы удивлялись все больше и больше.

Нахождение длины

С точки зрения lua длина строки это вовсе не количество символов, а количество байт, что усложняет работу с юникодными строками.

Для таблиц все сложнее:

Оператор длины(#) определяет длину последовательности, а не таблицы. Иначе говоря, для определения длины lua перебирыет все ключи начиная с 1 и заканчивая на месте разрыва числовой последовательности. Цитата:

Unless a __len metamethod is given, the length of a table t is only defined if the table is a sequence, that is, the set of its positive numeric keys is equal to {1..n} for some non-negative integer n.

Пример:

print(#{[0]='a', [3]='b', [1]='c'})
> 1

Плюс ко всему, очень странное поведение при наличии nil в таблице:

print(#{10, 20, nil, 40})
> 4

print(#{10, 20, nil})
> 2

В спецификации языка указано что при сохранении nil в таблицу возможно неопределенное поведение, при итерировании, получении длины итд. Правила подсчета длины для LuaC:

# When the last item in the array part is nil, the result of `#` is the lenght of the shortest valid seqence found by binsearching the array part for the first nil-followed key.
# When the last item in the array part is not nil AND the hash part is empty, the result of # is the physical lenght of the array part.
# When the last item in the array part is not nil AND the hash part is NOT empty, the result of # is the lenght of the shortest valid seqence found by binsearching the hash part for for the first nil-followed key (that is such positive integer i that `t[i] ~= nil` and `t[i+1] == nil`), assuming that the array part is full of non-nils(!).

Для хранения nil предлагается использовать кучу обходных путей, один неудобней другого. В каждом из методов используется оборачивание таблицы, в объект с кучей nil-логики.

Табличные литералы.

В lua элемент таблицы можно инициализировать двумя способами: с указанием ключа и без укзания Интересные вещи начинаются когда оба способа смешиваются внутри одного табличного литерала:

> for k,v in pairs({1, 2, [2]="hello", 3}) do print(k, v) end
1   1
2   2
3   3

> for k,v in pairs({1, 2, [4]="hello", 3}) do print(k, v) end
1   1
2   2
3   3
4   hello

> for k,v in pairs({[1]="fsdf", 2, [2]="hello", 3}) do print(k, v) end
1   2
2   3

> for k,v in pairs({["1"]="fsdf", 2, [2]="hello", 3}) do print(k, v) end
1   2
2   3
1   fsdf

Т.е. порядок инициализации такой:

  1. Инициализируются все элементы с указанием ключей
  2. Инициализируются элементы без указания ключа. При этом:
    • индекс-ключ генерируется НЕ на основе реальной позиции в табличном литерале, а на основе позиции в литерале без записей с указанными ключами
    • существующие записи затираются

Описание фичи здесь: http://www.lua.org/manual/5.3/manual.html#3.4.9

Параметры функции

Угадайте что выведет print в данном случае

t = {}
function t:f(self) print(self) end
t:f()

А здесь?

function f(p, p) print(p) end
f("hello, world!")

В обоих случаях правильный ответ - nil.

Первый случай транслируется в:

t = {}
t:f = function (self, self) print(self) end
t:f()

В Lua действуют правила видимости лексического контекста, согласно которым последний объявленный в текущем контексте идентификатор перекрывает собой все одноименные идентификаторы определенные ранее, это правило распространяется и на параметры функции. Поэтому внутри функции обращаясь к self мы обращаемся ко второму self, который перекрывает собой первый self Аналогичная ситуация и во втором случае

Multiple Assignment

Угадайте почему print выведет false в данном случае? x, x = 0 local y, y = 0 print(x == y)

В lua глобальные и локальные присвояения - разные по синтаксису и по семантике конструкции: varlist '=' explist

'local' namelist ('=' explist)?

В обоих случаях переменные без соответствующего значения для присвоения инициализируются nil'ами. Однако, при глобальном assignment присвоение значений для одноименных переменных идет только для их первых обявлений, т.е:

a, b, a, b = 1, 2, 3, 4
print(a, b)

> 1   2

Объясняется это тем, что интерпретатор Lua выполняет глобальные присвоения в обратном порядке.

Также необходимо помнить что перед неопсредственным присвоением расчитываются все выражения. Пример:

a = {}
i = 3
i, a[i] = i+1, 20
print(i, a[3], a[4])

> 4   20  nil

Как следствие, explist в обоих случаях вычисляется полностью в не зависимости от размера списка переменных для присвоения:

function f() print("Hello side effects!") end
local x = 1, f()

Pre-eval для выражений

Упреждающий eval выполняется для любого explist.

Это вовсе не означает что eval произойдет для всех подвыражений, опреации or/and делают eval своего второго аргумента по необходимости:

a or b

=>

tmp = eval(a)
if (tmp == nil)
    tmp = eval(b)

a and b

=>

tmp = eval(a)
if (tmp != nil)
    tmp = eval(b)

Видимость переменных repeat-until

В данной конструкции область видимости переменных тела цикла распространяется и на условие выхода (until-выражение). Данный цикл сделает одну итерацию:

x = false
repeat
    local x = true
until x

Return list adjustement

Скобки порой значят больше чем кажется:

function f() return 2, 3 end
a,b,c = 1, (f())
print(a, b, c)

> 1, 2, nil

Убираем "лишние" скобки:

function f() return 2, 3 end
a,b,c = 1, f()
print(a, b, c)

> 1, 2, 3

Объяснение простое: в Lua любое выражение, заключенное в скобки, гарантировано возвращает +одно+ значение.

Adjustement для множественных значений

Множественные значения могут вести себя весьма необычно. Рассмотрим простой пример:

function f(...) return ... end
t = { "a", f("b", "c", "d") }
for k, v in pairs(t) do print(k, v) end

>  1   a
>  2   b
>  3   c
>  4   d

Вынесем d из списка параметров:

function f(...) return ... end
t = { "a", f("b", "c"), "d" }
for k, v in pairs(t) do print(k, v) end

>  1   a
>  2   b
>  3   d

multivalue = vararg-параметр(...) или вызов функции, возвращающей последовательность значений B. Если в конце последовательности A стоит multivalue, то результатом будет конкатенация последовательностей (A + B). Если multivalue стоит не в конце, то первое значение из B встраивается в последовательность A. Если последовательность B пустая, то она заменяется на nil во всех случаях.

function f( ... )
    a, b, c = ..., ...
    print(a, b, c)
end

f(1, 2)

>  1, 1, 2

Необязательная точка с запятой

К сожалению, иногда очень даже обязательная. Этот код не будет работать:

t = { f = function (...) print(...) end }
t2 = t
(t).f("Hello world!")

> lua:2: attempt to call global 't' (a table value)

добавляем ';':

t = { f = function (...) print(...) end }
t2 = t;
(t).f("Hello world!")

> Hello world!

Трансляция local function

Сколько раз сработает print?

local f = function (i)
    if (i == 3 or not f) then return end
    print(i)
    f(i + 1);
end
f(0)

Ответ: нисколько. Убираем local:

f = function (i)
    if (i == 3 or not f) then return end
    print(i)
    f(i + 1);
end
f(0)

> 0
> 1
> 2

Здесь проявляются особенности трансляции local function:

 The statement

     function f () body end

translates to

     f = function () body end

The statement

     function t.a.b.c.f () body end

translates to

     t.a.b.c.f = function () body end

The statement

     local function f () body end

translates to

     local f; f = function () body end

not to

     local f = function () body end

Связывание контекста при объявлении функции

Связывание происходит в момент объявления функции и работает только для локалов, а точнее в момент eval'а функционального литерала, т.к все объявления функций преобразуются в assignment.

function f() print(l) end
local l = 2
f()

> nil

Проблемы стандартных реализаций , наши решения

тут пишем, почему мы выбрали именно luaj и последствия

Реализации lua, коорые допустимо рассматривать:

  • luac - оригинальный lua
  • luajit - jit реализация, самый быстрый интерпретатор
  • luaj - lua on JVM

ограничения luajit

Must read! - http://luapower.com/luajit-notes.html

Lua has 2GB addressing limit!

Получается, что мы не можем в luajit адресовать больше 2GB. WTF??? Например у нас 4 процесса/ядра - значит не больше 2*4=8GB у нас памяти, даже если её реально 32.

Мы должны выделять всю требуемую память во внешних по отношению к lua VM структурах, и вынуждены заниматься копированием.

Нужно было обмозговать эту проблему , т.к. в VM собирались/хотели использоваться именно luajit и это могло стать важным ограничением.

Варианты

ссылки

мы решили оказаться от luajit в пользу LuaJ производительность

Funkuch.lua
luajit         7s
luaj (jit)    23s
luaC         135s

Могло быть и хуже, а тут хоть с памятью порядок.

Подведём черту

Comments

Comments powered by Disqus
Skip to main content