Компилируемые в реальном времени DSL
для С++
Юрий Ефимочев
О себе
Архитектор в LogicNowСпециализация: высоконагруженные отказоустойчивые системы на C++
Бэкап-решение
Что такое DSL?Domain-specific language - это язык программирования с ограниченными возможностями, ориентированный на конкретную предметную область.
Достоинства и недостатки DSLДостоинства: • управление сложностью • скорость разработки • комуникация с экспертами • альтернативные парадигмы • динамическое выполнение Недостатки: • порог вхождения • эволюция в язык общего назначения • разработка и поддержка
Классификация DSL
• Внутренние• Внешние • Визуальные
• Интерпретируемые
• Компилируемые
Пример: тестированиеGiven( Node("a") (Node("b") (Node("c")) (Node("d"))) (Node("e") (Node("f")) (Node("g"))));
When().Filter("%f%");
Expect( Node("a") (Node("e") (Node("f"))));
a
b
c d
e
f g
a
e
f
Архитектура DSL
Программа
наDSL
Семантическаямодель
Целевойкод
Опционально
Постановка задачиДано:
~ 100 000 объектов
~ 500 параметров у объекта
Требуется:
получение актуальной статистики по произвольной выборке объектов
Примеры запросов
Count(true)
Count(LastSession.Timestamp < 2.days().ago())
Count(Version like ‘15.12.%’)
Count(LastSession.Status == SessionStatus::Failed)
Average(LastSession.Duration)
Sum(UsedStorage)
Архитектура решения
Expression AST Result
Data
Синтаксический анализ
Инструментарий:• flex & bison• antlr• boost::spirit
Синтаксический анализ%start Start;
AtomicExpression:IntegerNumber { $$.Value = m_builder.CreateInteger($1.Name); } |Identifier { $$.Value = m_builder.CreateVariable($1.Name) };
AddSubExpression:AtomicExpression |AddSubExpression Plus AddSubExpression { m_builder.CreateAddNode(...); } |AddSubExpression Minus AddSubExpression { m_builder.CreateSubNode(...); };
Start:AddSubExpression { result.AstTree.reset($1.Value); };
AST
Count(time > 2.days().ago()) Count
time ago
days
2
>
2.days().ago() = now - 2.days()
2.days() = 2 * 86400
AST
Count(time > 2.days().ago()) Count
time -
*
86400
>
now
2.days().ago() = now - 2.days()
2.days() = 2 * 864002
Реализация объектов AST class IntegerNode : public IAstNode { public: IntegerNode(int const value) : m_value(value) { }
private: virtual Variant Compile(IDataProvider& /*dataProvider*/) const { return Variant(m_value); }
int const m_value; };
Реализация объектов AST class VariableNode : public IAstNode { public: VariableNode(std::string const& name) : m_name(name) { }
private: virtual Variant Compile(IDataProvider& dataProvider) const { return Variant(dataProvider.GetVariableValue(m_name)); }
private: std::string const m_name; };
Реализация объектов AST class AddNode : public IAstNode { public: AddNode(IAstNodePtr left, IAstNodePtr right) : m_left(std::move(left)), m_right(std::move(right)) { }
private: virtual Variant Compile(IDataProvider& dataProvider) const { return m_left->Compile(dataProvider) + m_right->Compile(dataProvider); }
private: IAstNodePtr m_left; IAstNodePtr m_right;};
AST
Count(time > 2.days().ago()) Count
time -
*
86400
>
now
2.days().ago() = now - 2.days()
2.days() = 2 * 864002
Архитектура LLVM
Что такое LLVM IR?Особенности: • обобщенная система команд • функции • типы данных
• простые типы• указатели • массивы
• статическая типизация • SSA(static single assignment) нотация
LLVM IR bool MoveNext(); int GetValue();
int main() { int sum = 0;
while (MoveNext()) { sum += GetValue(); }
return sum; }
1 declare i1 @move_next() 2 declare i64 @get_value() 3 4 define i64 @main() 5 { 6 entry: 7 br label %loop_condition 8 9 loop_condition: 10 %1 = phi i64 [ 0, %entry ], [ %4, %loop_body ] 11 %2 = call i1 @move_next() 12 br i1 %2, label %loop_body, label %loop_exit 13 14 loop_body: 15 %3 = call i64 @get_value() 16 %4 = add i64 %3, %1 17 br label %loop_condition 18 19 loop_exit: 20 ret i64 %1 21 }
Архитектура решения
Expression AST LLVM IR AST
Native code
Data descriptor
Data provider
Result
Data
Реализация AST class IntegerNode : public IAstNode { public: IntegerNode(int const value) : m_value(value) { }
private: virtual CompiledNode Compile(IDataProvider& context) const { return CompiledNode(DataType::Integer, context.GetBuilder().getInt64(m_value)); }
int const m_value; };
Реализация AST class VariableNode : public IAstNode { public: VariableNode(std::string const& name) : m_name(name) { }
private: virtual CompiledNode Compile(ICompilationContext& context) const { return context.GetVariable(m_name); }
private: std::string const m_name; };
Реализация AST class AddNode : public IAstNode { public: AddNode(IAstNodePtr left, IAstNodePtr right) : m_left(std::move(left)), m_right(std::move(right)) { }
private: virtual CompiledNode Compile(ICompilationContext& context) const { CompiledNode left = m_left->Compile(context); CompiledNode right = m_right->Compile(context); DataType::Enum const effectiveType = GetEffectiveType(left, right); return CompiledNode(effectiveType, context.GetBuilder().CreateAdd(left, right)); }
private: IAstNodePtr m_left; IAstNodePtr m_right;};
Производительность
Interpreted 1734 мсLLVM 31 мсLLVM(precompiled) 28 мсNative 21 мс
Count(x < 50 && x > 22 || x == 77), size = 1000000
Кодогенерация
Инструментарий:• LLVM• Lua• Chrome V8
Производительность
Interpreted 1734 мсLua 309 мсLua(precompiled) 276 мсLLVM 31 мсLLVM(precompiled) 28 мсNative 21 мс
Count(x < 50 && x > 22 || x == 77), size = 1000000
Итоги
Плюсы:• простота• скорость разработки• Производительность
Минусы:• размер бинарного файла (~ 15 Mb)• реализация местами сложна• нативный код
?
Top Related