介绍
这是2020年后,所以happy new year给你所有!
我LLVM一个巨大的风扇,因为11年前,当我开始玩它来JIT data structures如AVLS,然后稍后JIT限制AST树和to从TensorFlow图JIT本机代码。此后,LLVM演变成最重要的编译器框架的生态系统之一,是由很多重要的开源项目采用了时下。
一个很酷的项目,我最近才知道的就是Gandiva。Gandiva被开发Dremio再后来donated to Apache Arrow(荣誉给Dremio团队为)。Gandiva的主要思想是,它提供了一个编译器生成LLVM IR可以在分批操作Apache的箭。Gandivawas written in C++ and comes with a lot of different functions implemented to build an expression tree that can be JIT’ed using LLVM. One nice feature of this design is that it can use LLVM to automatically optimize complex expressions, add native target platform vectorization such as AVX while operating on Arrow batches and execute native code to evaluate the expressions.
下面的图片提供了Gandiva的概述:

在这篇文章中我将建立一个非常简单的表达式解析器支持一亚洲金博宝组有限的,我会用它来筛选数据框大熊猫操作。
Building simple expression with Gandiva
在这一部分,我将展示如何创建使用树构建手动Gandiva一个简单的表达。
使用Gandiva Python绑定到JIT和表达
建立我们的解析器和表达式生成器表达式之前,让我们手动建立与Gandiva一个简单的表达。首先,我们将创建一个简单的熊猫数据框以数字从0.0到9.0:
进口熊猫作为PD进口pyarrow为PA进口pyarrow.gandiva作为gandiva#创建一个简单的熊猫数据帧DF = pd.DataFrame({ “×”:[1.0 * I为i的范围(10)]})表= pa.Table.from_pandas(DF)架构= pa.Schema.from_pandas(DF)
我们转换的数据帧到箭头表,需要注意的是,在这种情况下,它是一个零复制操作,箭不从熊猫复制数据和复制数据帧是很重要的。后来我们得到了模式
from the table, that contains column types and other metadata.
在那之后,我们要使用Gandiva建立下面的表达式来过滤数据:
(X> 2.0)和(x <6.0)
这个表达式将使用节点从Gandiva可以了:
助洗剂= gandiva.TreeExprBuilder()#参考列的 “x” node_x = builder.make_field(table.schema.field( “×”))#提出两个文字:2.0和6.0 2 = builder.make_literal(2.0,pa.float64())6 = builder.make_literal(6.0,pa.float64())#为 “X> 2.0” gt_five_node = builder.make_function一个函数( “GREATER_THAN”,[node_x,两],pa.bool_())#创建 “×<6.0” 的函数lt_ten_node = builder.make_function( “LESS_THAN”,[node_x,六],pa.bool_())#创建一个 “和” 节点,为“(X> 2.0)和(x <6.0)” and_node = builder.make_and([gt_five_node,lt_ten_node])#使表达的条件,并创建一个过滤条件= builder.make_condition(and_node)filter_ = gandiva.make_filter(table.schema,条件)
此代码现在看起来有点复杂,但它是很容易理解。我们基本上是创建一个树将代表我们前面显示的表达式的节点。这里是什么样子的图示:
检查所生成的LLVM IR
Unfortunately, haven’t found a way to dump the LLVM IR that was generated using the Arrow’s Python bindings, however, we can just use the C++ API to build the same tree and then look at the generated LLVM IR:
auto field_x = field("x", float32()); auto schema = arrow::schema({field_x}); auto node_x = TreeExprBuilder::MakeField(field_x); auto two = TreeExprBuilder::MakeLiteral((float_t)2.0); auto six = TreeExprBuilder::MakeLiteral((float_t)6.0); auto gt_five_node = TreeExprBuilder::MakeFunction("greater_than", {node_x, two}, arrow::boolean()); auto lt_ten_node = TreeExprBuilder::MakeFunction("less_than", {node_x, six}, arrow::boolean()); auto and_node = TreeExprBuilder::MakeAnd({gt_five_node, lt_ten_node}); auto condition = TreeExprBuilder::MakeCondition(and_node); std::shared_ptrfilter; auto status = Filter::Make(schema, condition, TestConfiguration(), &filter);
上面的代码是一样的Python代码,但使用C ++ Gandiva API。现在,我们建立了树,C ++,我们可以得到LLVM模块和转储IR代码它。所产生的IR是充分的样板代码和从Gandiva注册表中JIT'ed功能,但是重要的部分是如下显示:
;功能ATTRS:alwaysinline norecurse非展开readnone SSP uwtable限定内部zeroext I1 @ less_than_float32_float32(浮点,浮点)local_unnamed_addr#0 {%3 = FCMP OLT浮子%0%1 RET I1%3};功能ATTRS:alwaysinline norecurse非展开readnone SSP uwtable限定内部zeroext I1 @ greater_than_float32_float32(浮点,浮点)local_unnamed_addr#0 {%3 = FCMP OGT浮子%0%1 RET I1%3}(...)%×=负载浮,浮子*%11%greater_than_float32_float32 =调用I1 @ greater_than_float32_float32(浮动%的x,浮子2.000000e + 00)(...)%X11 =负载浮子,浮子*%15%less_than_float32_float32 =调用I1 @ less_than_float32_float32(浮动%X11,浮子6.000000e + 00)
As you can see, on the IR we can see the call to the functionsless_than_float32_float_32
和greater_than_float32_float32
这是(在这种情况下很简单的)Gandiva功能做浮动比亚洲金博宝较。通过查看函数名前缀注意函数的专业化。
什么是颇为有趣的是,LLVM将适用于所有的优化在这个代码,它会为目标平台的高效的本地代码同时戈黛娃和LLVM将采取确保内存对齐将成为扩展,如AVX用于正确的护理矢量。
这IR代码我发现是不是真正执行了一个,但优化的一个。和在优化的一个我们可以看到,内联LLVM的功能,如显示在下面的优化代码的一部分:
%x.us = load float, float* %10, align 4 %11 = fcmp ogt float %x.us, 2.000000e+00 %12 = fcmp olt float %x.us, 6.000000e+00 %not.or.cond = and i1 %12, %11
你可以看到,表达的是现在简单多了优化后的LLVM应用其强大的优化和内联很多Gandiva funcions的。
Building a Pandas filter expression JIT with Gandiva
现在,我们希望能够实现,因为大熊猫类似的东西DataFrame.query()
使用Gandiva功能。我们将面临的第一个问题是,我们需要分析一个字符串,如(X> 2.0)和(x <6.0)
,later we will have to build the Gandiva expression tree using the tree builder from Gandiva and then evaluate that expression on arrow data.
没有w, instead of implementing a full parsing of the expression string, I’ll use the Python AST module to parse valid Python code and build an Abstract Syntax Tree (AST) of that expression, that I’ll be later using to emit the Gandiva/LLVM nodes.
解析字符串的繁重的工作将被委托给Python的AST模块和我们的工作将主要走在这棵树,并基于该语法树发出Gandiva节点。访问此Python的AST树的节点和发射节点Gandiva的代码如下所示:
类LLVMGandivaVisitor(ast.NodeVisitor):对于f中self.builder.make_field(F):DEF __init __(个体,df_table):self.table = df_table self.builder = gandiva.TreeExprBuilder()self.columns = {f.nameself.table.schema} self.compare_ops = { “GT”: “GREATER_THAN”, “LT”: “LESS_THAN”,} self.bin_ops = { “BITAND”:self.builder.make_and, “BITOR”:self.builder。make_or, } def visit_Module(self, node): return self.visit(node.body[0]) def visit_BinOp(self, node): left = self.visit(node.left) right = self.visit(node.right) op_name = node.op.__class__.__name__ gandiva_bin_op = self.bin_ops[op_name] return gandiva_bin_op([left, right]) def visit_Compare(self, node): op = node.ops[0] op_name = op.__class__.__name__ gandiva_comp_op = self.compare_ops[op_name] comparators = self.visit(node.comparators[0]) left = self.visit(node.left) return self.builder.make_function(gandiva_comp_op, [left, comparators], pa.bool_()) def visit_Num(self, node): return self.builder.make_literal(node.n, pa.float64()) def visit_Expr(self, node): return self.visit(node.value) def visit_Name(self, node): return self.columns[node.id] def generic_visit(self, node): return node def evaluate_filter(self, llvm_mod): condition = self.builder.make_condition(llvm_mod) filter_ = gandiva.make_filter(self.table.schema, condition) result = filter_.evaluate(self.table.to_batches()[0], pa.default_memory_pool()) arr = result.to_array() pd_result = arr.to_numpy() return pd_result @staticmethod def gandiva_query(df, query): df_table = pa.Table.from_pandas(df) llvm_gandiva_visitor = LLVMGandivaVisitor(df_table) mod_f = ast.parse(query) llvm_mod = llvm_gandiva_visitor.visit(mod_f) results = llvm_gandiva_visitor.evaluate_filter(llvm_mod) return results
正如你所看到的,它的代码,我不支持每一个可能的Python表达式,但它的一个子集轻微非常简单。亚洲金博宝我们做这个班什么是基本的比较和BinOps(二元运算)的Gandiva节点的Python AST的转换节点,。我也正在改变的语义&
和|
运营商来表示AND和OR分别如在熊猫查询()
功能。
注册为熊猫扩展
下一步是使用创建一个简单的熊猫扩展GAñdiva_query()
方法,我们创建了:
@ pd.api.extensions.register_dataframe_accessor( “gandiva”)类GandivaAcessor:高清__init __(自我,pandas_obj):self.pandas_obj = pandas_obj高清查询(个体经营,查询):返回LLVMGandivaVisitor.gandiva_query(self.pandas_obj,查询)
这就是它,现在我们可以使用这个扩展做的事情,例如:
DF = pd.DataFrame({ “一”:[1.0 * I为i的范围(n大小)]})=结果df.gandiva.query( “A> 10.0”)
正如我们已经注册了熊猫的扩展名为GAñdiva
这是现在的大熊猫DataFrames的一等公民。
现在,让我们创建一个500万辆花车数据框,并使用新查询()
方法对其进行过滤:
DF = pd.DataFrame({ “一”:[1.0 * I为i的范围(50000000)]})df.gandiva.query( “一<4.0”)#这将输出:#阵列([0,1,2,3],D型细胞= UINT32)
请注意,返回的值是满足我们实施条件的指标,因此它比大熊猫不同查询()
返回该数据已过滤。
我做了一些基准测试,发现Gandiva通常总是比熊猫快,但我会留下适当的基准上Gandiva下一个岗位作为这个职位是向您展示如何使用它来表达JIT。
而已 !我希望你喜欢这个职位,因为我喜欢探索Gandiva。看来,我们将可能有更多的工具来了Gandiva加速,专为SQL解析/投影/ JIT编译。Gandiva比我刚才给得多了,但你可以现在就开始了解更多的架构和如何建立表达式树的。
- 基督教S. Perone