Upload
ontico
View
97
Download
3
Embed Size (px)
Citation preview
Что такое Rspamd
• Система фильтрации спама с ориентацией на производительность
• OpenSource проект
• Плагины и правила написаны на языке Lua
• Ядро написано на plain (old) C
Lua и C В Rspamd
Lua
C
Соотношение Lua и C По строкам кода
0
35000
70000
105000
140000
Plain C
LuaAssembly Other
Perl
Соотношение Lua и C По количеству плагинов
0
8
15
23
30
6
26
Lua C
Что написано на Lua• Правила, а также комбинации регулярных выражений
• Большая часть плагинов:
• Проверка DNS списков
• Взаимодействие с Redis (репутация, “серые” списки, динамические настройки, список ответов, динамические лимиты)
• Работа с внешними сервисами (антивирусы, запись в ClickHouse итд)
• Контент-фильтрация
• Работа с нейросетями и классификацией текстов
• Работа с SpamAssassin правилами
Почему Lua?
Экскурс в историю
K&R определение функции
Странный синтаксис переменных
Что это?
Надо больше макросов!
Похоже, это обычный C
Правило на Lua
Встраиваемый язык Каким он должен быть
Встраиваемый язык Каким он должен быть
• Минимум внешних зависимостей (JavaScript)
Встраиваемый язык Каким он должен быть
• Минимум внешних зависимостей (JavaScript)
• Простота синтаксиса (Perl)
Встраиваемый язык Каким он должен быть
• Минимум внешних зависимостей (JavaScript)
• Простота синтаксиса (Perl)
• Скорость работы (Python, Guile, TCL)
Встраиваемый язык Каким он должен быть
• Минимум внешних зависимостей (JavaScript)
• Простота синтаксиса (Perl)
• Скорость работы (Python, Guile, TCL)
• Скорость переключения между C и встраиваемым языком
Что дал переход на Lua
Что дал переход на Lua
• Появилось несколько активных авторов плагинов и правил на Lua
Что дал переход на Lua
• Появилось несколько активных авторов плагинов и правил на Lua
• Появились юнит-тесты на Lua
Что дал переход на Lua
• Появилось несколько активных авторов плагинов и правил на Lua
• Появились юнит-тесты на Lua
• Текущий подход к разработке: писать Lua биндинги на Си, а основную логику на Lua
Что дал переход на Lua
• Появилось несколько активных авторов плагинов и правил на Lua
• Появились юнит-тесты на Lua
• Текущий подход к разработке: писать Lua биндинги на Си, а основную логику на Lua
• Упростилось написание утилит командной строки
Особенности Lua
Кривая обучения Lua
Начал писать на Lua
Обучение LuaThat was my first ever attempt at Lua, I asked you to add halfOpen support to your lua_tcp module, which you did within about 30 mins and I had the plugin written an working within an hour or so.
Обучение Lua
• Плагин опроса внешнего сервиса DCC:
That was my first ever attempt at Lua, I asked you to add halfOpen support to your lua_tcp module, which you did within about 30 mins and I had the plugin written an working within an hour or so.
Обучение Lua
• Плагин опроса внешнего сервиса DCC:
• написан за час времени
That was my first ever attempt at Lua, I asked you to add halfOpen support to your lua_tcp module, which you did within about 30 mins and I had the plugin written an working within an hour or so.
Обучение Lua
• Плагин опроса внешнего сервиса DCC:
• написан за час времени
• человеком, без знаний Lua
That was my first ever attempt at Lua, I asked you to add halfOpen support to your lua_tcp module, which you did within about 30 mins and I had the plugin written an working within an hour or so.
Обучение Lua
• Плагин опроса внешнего сервиса DCC:
• написан за час времени
• человеком, без знаний Lua
• еще 30 минут заняла адаптация биндингов на C
That was my first ever attempt at Lua, I asked you to add halfOpen support to your lua_tcp module, which you did within about 30 mins and I had the plugin written an working within an hour or so.
Обучение Lua
• Плагин опроса внешнего сервиса DCC:
• написан за час времени
• человеком, без знаний Lua
• еще 30 минут заняла адаптация биндингов на C
• аналог на перле - больше 1k строк кода
That was my first ever attempt at Lua, I asked you to add halfOpen support to your lua_tcp module, which you did within about 30 mins and I had the plugin written an working within an hour or so.
Некоторые особенности Lua Общие характеристики языка
Некоторые особенности Lua Общие характеристики языка
• Крайне простой синтаксис (20 правил в BNF грамматике)
Некоторые особенности Lua Общие характеристики языка
• Крайне простой синтаксис (20 правил в BNF грамматике)
• Таблицы - универсальный инструмент работы с данными
Некоторые особенности Lua Общие характеристики языка
• Крайне простой синтаксис (20 правил в BNF грамматике)
• Таблицы - универсальный инструмент работы с данными
• Функции - объекты первого рода (возможны функциональные конструкции и замыкания)
Некоторые особенности Lua Общие характеристики языка
• Крайне простой синтаксис (20 правил в BNF грамматике)
• Таблицы - универсальный инструмент работы с данными
• Функции - объекты первого рода (возможны функциональные конструкции и замыкания)
• Стандартная библиотека содержит только необходимый минимум функций
Некоторые особенности Lua Общие характеристики языка
• Крайне простой синтаксис (20 правил в BNF грамматике)
• Таблицы - универсальный инструмент работы с данными
• Функции - объекты первого рода (возможны функциональные конструкции и замыкания)
• Стандартная библиотека содержит только необходимый минимум функций
• Динамическая строгая типизация
Синтаксис Lua Основные элементы
Синтаксис Lua Основные элементы
• Переменные:
Синтаксис Lua Основные элементы
• Переменные: local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
Синтаксис Lua Основные элементы
• Переменные:
• Условия:
local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
Синтаксис Lua Основные элементы
• Переменные:
• Условия:
local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
if not ret then -- can use ‘not’, ‘and’, ‘or’ here…elseif ret ~= 10 then -- note ~= for ‘not equal’ operatorend
Синтаксис Lua Основные элементы
• Переменные:
• Условия:
• Циклы:
local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
if not ret then -- can use ‘not’, ‘and’, ‘or’ here…elseif ret ~= 10 then -- note ~= for ‘not equal’ operatorend
Синтаксис Lua Основные элементы
• Переменные:
• Условия:
• Циклы:
local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
if not ret then -- can use ‘not’, ‘and’, ‘or’ here…elseif ret ~= 10 then -- note ~= for ‘not equal’ operatorend
for k,m in pairs(opts) do … end -- Iterate over keyed table a[‘key’] = valuefor _,i in ipairs(images) do … end -- Iterate over array table a[1] = valuefor i=1,10 do … end -- Count from 1 to 10
Синтаксис Lua Основные элементы
• Переменные:
• Условия:
• Циклы:
• Таблицы:
local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
if not ret then -- can use ‘not’, ‘and’, ‘or’ here…elseif ret ~= 10 then -- note ~= for ‘not equal’ operatorend
for k,m in pairs(opts) do … end -- Iterate over keyed table a[‘key’] = valuefor _,i in ipairs(images) do … end -- Iterate over array table a[1] = valuefor i=1,10 do … end -- Count from 1 to 10
Синтаксис Lua Основные элементы
• Переменные:
• Условия:
• Циклы:
• Таблицы:
local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
if not ret then -- can use ‘not’, ‘and’, ‘or’ here…elseif ret ~= 10 then -- note ~= for ‘not equal’ operatorend
for k,m in pairs(opts) do … end -- Iterate over keyed table a[‘key’] = valuefor _,i in ipairs(images) do … end -- Iterate over array table a[1] = valuefor i=1,10 do … end -- Count from 1 to 10
local options = { [1] = ‘value’, [‘key’] = 1, -- Numbers starts from 1 another_key = function(task) … end, -- Functions can be values [2] = {} -- Other tables can be values} -- Can have both numbers and strings as key and anything as values
Синтаксис Lua Основные элементы
• Переменные:
• Условия:
• Циклы:
• Таблицы:
• Функции:
local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
if not ret then -- can use ‘not’, ‘and’, ‘or’ here…elseif ret ~= 10 then -- note ~= for ‘not equal’ operatorend
for k,m in pairs(opts) do … end -- Iterate over keyed table a[‘key’] = valuefor _,i in ipairs(images) do … end -- Iterate over array table a[1] = valuefor i=1,10 do … end -- Count from 1 to 10
local options = { [1] = ‘value’, [‘key’] = 1, -- Numbers starts from 1 another_key = function(task) … end, -- Functions can be values [2] = {} -- Other tables can be values} -- Can have both numbers and strings as key and anything as values
Синтаксис Lua Основные элементы
• Переменные:
• Условия:
• Циклы:
• Таблицы:
• Функции:
local ret = false -- Generic variablelocal rules = {} -- Empty tablelocal rspamd_logger = require “rspamd_logger" -- Load rspamd module
if not ret then -- can use ‘not’, ‘and’, ‘or’ here…elseif ret ~= 10 then -- note ~= for ‘not equal’ operatorend
for k,m in pairs(opts) do … end -- Iterate over keyed table a[‘key’] = valuefor _,i in ipairs(images) do … end -- Iterate over array table a[1] = valuefor i=1,10 do … end -- Count from 1 to 10
local options = { [1] = ‘value’, [‘key’] = 1, -- Numbers starts from 1 another_key = function(task) … end, -- Functions can be values [2] = {} -- Other tables can be values} -- Can have both numbers and strings as key and anything as values
local function something(task) -- Normal definition local cb = function(data) -- Functions can be nested … endend
Таблицы в Lua
Таблицы в Lua Основные определения
Таблицы в Lua Основные определения
• Значения - все, кроме nil:
Таблицы в Lua Основные определения
• Значения - все, кроме nil: local t = { 1, -- number 'test', -- string function() end, -- function {1, 2, 3} -- another table task:get_mempool(), -- userdata}
Таблицы в Lua Основные определения
• Значения - все, кроме nil: local t = { 1, -- number 'test', -- string function() end, -- function {1, 2, 3} -- another table task:get_mempool(), -- userdata}
• Ключи - строки и числа:
Таблицы в Lua Основные определения
• Значения - все, кроме nil: local t = { 1, -- number 'test', -- string function() end, -- function {1, 2, 3} -- another table task:get_mempool(), -- userdata}
local t = { 1, -- 1 [3] = {1,2}, test = function() end, ['spaces in key'] = 'abc', [1.2] = 3, [-1] = 4,}
• Ключи - строки и числа:
Таблицы в Lua
table
local table = { a = 1, b = 2, [1] = 3, [2] = function() end, [0] = true }
3
function() end
true
1
2
12
0‘a’‘b’
Массив Хеш таблица!
Таблицы в Lua Итерация
local t = { a = 1, b = 2, [1] = 3, [2] = function() end, [0] = true }
Массив
Таблица целиком
1..2
Метатаблицы Когда просто таблиц недостаточно
• Задают общие свойства для других таблиц
• Примерно соответствуют Prototype в JavaScript
• Позволяют задавать функции-методы для таблиц (через __newindex или __index)
• Могут также задавать операторы над таблицами (например, через __eq или __sum)
• Можно задавать метатаблицы для стандартных Lua типов (например, строк)
• Используются в C API для определения методов для типа userdata
Функции в Lua
Функции в Lua Основные элементы
Функции в Lua Основные элементы
• Могут быть вложенными:
Функции в Lua Основные элементы
• Могут быть вложенными: function foo(n) -- global function local var = function(m) -- function in var return m + n -- n is from `foo` end local function bar() -- another form of var return var(2) end return bar()end
Функции в Lua Основные элементы
• Могут быть вложенными: function foo(n) -- global function local var = function(m) -- function in var return m + n -- n is from `foo` end local function bar() -- another form of var return var(2) end return bar()end
• Могут быть аргументами и
возвращаться из функций:
Функции в Lua Основные элементы
• Могут быть вложенными: function foo(n) -- global function local var = function(m) -- function in var return m + n -- n is from `foo` end local function bar() -- another form of var return var(2) end return bar()end
local function f(cb) -- accepts callback function return function(args) -- accepts some args return cb(args) -- return callback function from args (decorator like) endend
• Могут быть аргументами и
возвращаться из функций:
Функции в Lua Замыкания
function test(n) local f = function() return n + 1 -- n is captured end
n = n + 1 -- n in f is also modified return а end
test(1) -- Returns function closure test(1)() -- Returns 3
Функции в Lua Замыкания
function test(n) local f = function() return n + 1 -- n is captured end
n = n + 1 -- n in f is also modified return а end
test(1) -- Returns function closure test(1)() -- Returns 3
Замыкание
Функции в Lua Замыкания
function test(n) local f = function() return n + 1 -- n is captured end
n = n + 1 -- n in f is also modified return а end
test(1) -- Returns function closure test(1)() -- Returns 3
Замыкание
Функции в Lua Замыкания
function test(n) local f = function() return n + 1 -- n is captured end
n = n + 1 -- n in f is also modified return а end
test(1) -- Returns function closure test(1)() -- Returns 3
Замыкание
Переменные входят по ссылке
Функции в Lua Замыкания
function test(n) local f = function() return n + 1 -- n is captured end
n = n + 1 -- n in f is also modified return а end
test(1) -- Returns function closure test(1)() -- Returns 3
Замыкание
Переменные входят по ссылкеМожно возвращать функцию
Функции в Lua Замыкания
function test(n) local f = function() return n + 1 -- n is captured end
n = n + 1 -- n in f is also modified return а end
test(1) -- Returns function closure test(1)() -- Returns 3
Замыкание
Переменные входят по ссылке
Функция-замыкание
Можно возвращать функцию
Функции в Lua Замыкания
function test(n) local f = function() return n + 1 -- n is captured end
n = n + 1 -- n in f is also modified return а end
test(1) -- Returns function closure test(1)() -- Returns 3
Замыкание
Переменные входят по ссылке
Функция-замыканиеНепосредственно вызов
Можно возвращать функцию
Функции в Lua
• Позволяют писать в функциональном стиле (библиотека lua-fun)
• Замыкания работают также для C функций
• Передача по ссылке несколько необычна, но эффективна
• Время жизни замыкания ассоциируется с переменной, в которой оно хранится
Строки в Lua
Строки в Lua Передача строки из C
Глобальный хеш строк
s1s2s3
lua_pushstring (L, "hello");
hello
Копия
Хеш
hash(hello)
Поиск
Строки в Lua Передача строки из C
Глобальный хеш строк
s1s2s3
lua_pushstring (L, "hello");
hello
Вставкаhello
Строки в Lua
• Строки в Lua неизменяемы
• Сравнение строк - просто сравнение указателей: O(1)
• Создание и передача строк - дорогая операция
• Если нужно делать строку из кусков, то нужно использовать таблицу и table.concat
Строки в LuaСекунды
(меньше
- лучше)
0
75
150
225
300
Naive concat Table.concat
6.7c
274c
40x
Строки в Lua Передача текста
C C
Userdata
const char *ssize_t lenrefcounter
Взаимодействие Lua и C
Виртуальный стек Общий вид
1234
-4-3-2-1 lua_gettop() ⟹4
PushPop
Виртуальный стек Добавление значений
1234
-6-5-4-3
lua_pushnumbernumber
string
56
-2-1 lua_pushstring
Push
Виртуальный стек Извлечение значений
1234
-3-2-1
lua_pop(L, 3);number
string
56
Pop
Виртуальный стек Вызов функции
1234
-3-2-1
56
Внутренние переменые
number
string
45
-2-1
Аргументы
Возвращаемые значения
Проблемы работы со стеком
Проблемы работы со стеком
• Скорость: переключение занимает около сотни CPU cycles
Проблемы работы со стеком
• Скорость: переключение занимает около сотни CPU cycles
• Проблемы контроля: без специальных ключей компиляции легко получить низкоуровневые ошибки (вплоть до полного падения)
Проблемы работы со стеком
• Скорость: переключение занимает около сотни CPU cycles
• Проблемы контроля: без специальных ключей компиляции легко получить низкоуровневые ошибки (вплоть до полного падения)
Проблемы работы со стеком
• Скорость: переключение занимает около сотни CPU cycles
• Проблемы контроля: без специальных ключей компиляции легко получить низкоуровневые ошибки (вплоть до полного падения)
• Крайне неочевидный код
Проблемы работы со стеком
• Скорость: переключение занимает около сотни CPU cycles
• Проблемы контроля: без специальных ключей компиляции легко получить низкоуровневые ошибки (вплоть до полного падения)
• Крайне неочевидный код
• Есть ряд сложных моментов (итерация по таблице)
Пример из практики Итерация по таблице
lua_pushvalue (L, i); /* Push table on top */ lua_pushnil (L); /* Push nil to start iterate */
while (lua_next (L, -2)) { lua_pushvalue (L, -2); /* Copy key as it is special */
key = luaL_checkstring (L, -1); value = luaL_checkstring (L, -2);
lua_pop (L, 2); /* Remove key and value leaving original key */ }
lua_pop (L, 1); /* Remove table */
Пример из практики Итерация по таблице
lua_pushvalue (L, i); tabletop -1
Пример из практики Итерация по таблице
lua_pushvalue (L, i); lua_pushnil (L);
tabletop -2nil -1
Пример из практики Итерация по таблице
table -2nil -1
while (lua_next (L, -2))
table -3key -2
value -1
Предыдущий ключ
Новый ключ
Пример из практики Итерация по таблице
lua_pushvalue (L, -2); key = luaL_checkstring (L, -1); value = luaL_checkstring (L, -2);
table
-2key
-1value
key copy
-3-4
Пример из практики Итерация по таблице
lua_pop (L, 2);
table
-2key
-1value
key copy
-3-4
table
key -1-2
while (lua_next (L, -2))
Используется для следующей итерации
FFI вызовы кода на C
Плюсы FFI
• Очень быстро работают для простых типов (десяток циклов)
• Поддерживают все конструкции C99
• Поддерживают типы-обертки (boxed types), например 64-х битные целые
• Проще поддерживать
FFI вызовы Синтетические тесты
Секунды
(меньше
- лучше)
0
10
20
30
40
tanh(x) strcmp(s1, s2)Plain call FFI Plain call FFI
5.97c3.13c 4.38c
30.07c
10x
Пример из практики Оптимизация узких мест
local ffi if type(jit) == 'table' then ffi = require("ffi") ffi.cdef[[ int rspamd_re_cache_type_from_string (const char *str); int rspamd_re_cache_process_ffi (void *ptask, void *pre, int type, const char *type_data, int is_strong); ]] end
local function process_regexp_opt(re, task, re_type, header, strong) if type(jit) == 'table' then -- Use ffi call local itype = ffi.C.rspamd_re_cache_type_from_string(re_type)
if not strong then strong = 0 else string = 1 end local iret = ffi.C.rspamd_re_cache_process_ffi (task, re, itype, header, strong)
return tonumber(iret) else return task:process_regexp(re, re_type, header, strong) end end
Проверка LuaJIT
local ffi if type(jit) == 'table' then ffi = require("ffi") ffi.cdef[[ int rspamd_re_cache_type_from_string (const char *str); int rspamd_re_cache_process_ffi (void *ptask, void *pre, int type, const char *type_data, int is_strong); ]] end
local function process_regexp_opt(re, task, re_type, header, strong) if type(jit) == 'table' then -- Use ffi call local itype = ffi.C.rspamd_re_cache_type_from_string(re_type)
if not strong then strong = 0 else string = 1 end local iret = ffi.C.rspamd_re_cache_process_ffi (task, re, itype, header, strong)
return tonumber(iret) else return task:process_regexp(re, re_type, header, strong) end end
Определение С функций
Пример из практики Оптимизация узких мест
local ffi if type(jit) == 'table' then ffi = require("ffi") ffi.cdef[[ int rspamd_re_cache_type_from_string (const char *str); int rspamd_re_cache_process_ffi (void *ptask, void *pre, int type, const char *type_data, int is_strong); ]] end
local function process_regexp_opt(re, task, re_type, header, strong) if type(jit) == 'table' then -- Use ffi call local itype = ffi.C.rspamd_re_cache_type_from_string(re_type)
if not strong then strong = 0 else string = 1 end local iret = ffi.C.rspamd_re_cache_process_ffi (task, re, itype, header, strong)
return tonumber(iret) else return task:process_regexp(re, re_type, header, strong) end end
FFI call
Userdata to C pointer
bool -> int
Пример из практики Оптимизация узких мест
local ffi if type(jit) == 'table' then ffi = require("ffi") ffi.cdef[[ int rspamd_re_cache_type_from_string (const char *str); int rspamd_re_cache_process_ffi (void *ptask, void *pre, int type, const char *type_data, int is_strong); ]] end
local function process_regexp_opt(re, task, re_type, header, strong) if type(jit) == 'table' then -- Use ffi call local itype = ffi.C.rspamd_re_cache_type_from_string(re_type)
if not strong then strong = 0 else string = 1 end local iret = ffi.C.rspamd_re_cache_process_ffi (task, re, itype, header, strong)
return tonumber(iret) else return task:process_regexp(re, re_type, header, strong) end end
Plain call
Пример из практики Оптимизация узких мест
Оптимизация FFI вызововLua вызов
FFI вызов
Проблемы FFI
Проблемы FFI• Поддерживаются на ограниченном количестве архитектур (Sparc64)
Проблемы FFI• Поддерживаются на ограниченном количестве архитектур (Sparc64)
• В plain Lua требуют отдельного модуля FFI, LuaJIT поддерживает “из коробки”
Проблемы FFI• Поддерживаются на ограниченном количестве архитектур (Sparc64)
• В plain Lua требуют отдельного модуля FFI, LuaJIT поддерживает “из коробки”
• Нет проверки типов, нет контроля целостности памяти
Проблемы FFI• Поддерживаются на ограниченном количестве архитектур (Sparc64)
• В plain Lua требуют отдельного модуля FFI, LuaJIT поддерживает “из коробки”
• Нет проверки типов, нет контроля целостности памяти
Проблемы FFI• Поддерживаются на ограниченном количестве архитектур (Sparc64)
• В plain Lua требуют отдельного модуля FFI, LuaJIT поддерживает “из коробки”
• Нет проверки типов, нет контроля целостности памяти
Проблемы FFI• Поддерживаются на ограниченном количестве архитектур (Sparc64)
• В plain Lua требуют отдельного модуля FFI, LuaJIT поддерживает “из коробки”
• Нет проверки типов, нет контроля целостности памяти
• Не всегда быстрее
Проблемы FFI• Поддерживаются на ограниченном количестве архитектур (Sparc64)
• В plain Lua требуют отдельного модуля FFI, LuaJIT поддерживает “из коробки”
• Нет проверки типов, нет контроля целостности памяти
• Не всегда быстрее
• Очень сложно сделать sandbox
Выводы
Выводы
Выводы• Lua простой для изучения
Выводы• Lua простой для изучения
Выводы• Lua простой для изучения
• Lua удобен для встраивания
Выводы• Lua простой для изучения
• Lua удобен для встраивания
Выводы
Выводы• Основной инструмент - таблицы
Выводы• Основной инструмент - таблицы
Выводы• Основной инструмент - таблицы
• Основной инструмент - функции
Выводы• Основной инструмент - таблицы
• Основной инструмент - функции
Выводы
Выводы• Lua - быстрый язык (особенно LuaJIT)
Выводы• Lua - быстрый язык (особенно LuaJIT)
Выводы• Lua - быстрый язык (особенно LuaJIT)
• Некоторые оптимизации бывают опасны
Выводы• Lua - быстрый язык (особенно LuaJIT)
• Некоторые оптимизации бывают опасны