Почему мы выбрали LUA, сюрпризы луа
Что такое LUA, почему мы выбрали LUA
общий обзор языка и альтернатив ему в контексте нашей задачи
Сюрпризы языка lua, или вещи которые удивили, но поздно
список странностей обнаруженных в процессе работу над системой ;)
LUA оказался очень странным языком! ОЧЕНЬ! Я когда на нём писал не задумывался об этих пробламах совершенно! А тут нам пришлось иметь дело с его внутренностями, и мы удивлялись все больше и больше.
Нахождение длины
С точки зрения lua длина строки это вовсе не количество символов, а количество байт, что усложняет работу с юникодными строками.
Для таблиц все сложнее:
Оператор длины(#) определяет длину последовательности, а не таблицы. Иначе говоря, для определения длины lua перебирыет все ключи начиная с 1 и заканчивая на месте разрыва числовой последовательности. Цитата:
Unless a
__lenmetamethod 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
Т.е. порядок инициализации такой:
- Инициализируются все элементы с указанием ключей
- Инициализируются элементы без указания ключа. При этом:
- индекс-ключ генерируется НЕ на основе реальной позиции в табличном литерале, а на основе позиции в литерале без записей с указанными ключами
- существующие записи затираются
Описание фичи здесь: 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
Получается, что мы не можем в luajit адресовать больше 2GB. WTF??? Например у нас 4 процесса/ядра - значит не больше 2*4=8GB у нас памяти, даже если её реально 32.
Мы должны выделять всю требуемую память во внешних по отношению к lua VM структурах, и вынуждены заниматься копированием.
Нужно было обмозговать эту проблему , т.к. в VM собирались/хотели использоваться именно luajit и это могло стать важным ограничением.
Варианты
- решения malloc http://stackoverflow.com/questions/27015150/how-to-get-past-1gb-memory-limit-of-64-bit-luajit-on-linux
- тест https://gist.github.com/neomantra/9122165
- тест2 http://lua-users.org/lists/lua-l/2012-04/msg00729.html 32GB
ссылки
- https://gitorious.org/mediawiki/mediawiki-svn/commit/c47c46372e8277006e2d66ab5ec74f149e010bdc?diffmode=inline
- https://gist.github.com/nddrylliog/8722197
- http://www.canonware.com/pipermail/jemalloc-discuss/2014-February/000740.html
мы решили оказаться от luajit в пользу LuaJ производительность
Funkuch.lua luajit 7s luaj (jit) 23s luaC 135s
Могло быть и хуже, а тут хоть с памятью порядок.
Comments
Comments powered by Disqus