Gandiva,使用LLVM和箭JIT和评估熊猫表情

介绍

这是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的概述:

的Gandiva如何工作的概述。图片来自:https://www.dremio.com/announcing-gandiva-initiative-for-apache-arrow

在这篇文章中我将建立一个非常简单的表达式解析器支持一亚洲金博宝组有限的,我会用它来筛选数据框大熊猫操作。

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_ptr filter; 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_32greater_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

引用本文为:基督教S. Perone,“Gandiva,使用LLVM和箭JIT和评估熊猫表情,”在亚洲金博宝未知领域,19/01/2020,//www.cpetem.com/2020/01/gandiva-using-llvm-and-arrow-to-jit-and-evaluate-pandas-expressions/

PyData蒙特利尔幻灯片谈话:引擎盖下PyTorch

These are the slides of the talk I presented on PyData Montreal on Feb 25th. It was a pleasure to meet you all ! Thanks a lot toMaria亚历山大的邀请!

Cite this article as: Christian S. Perone, "PyData Montreal slides for the talk: PyTorch under the hood," in亚洲金博宝未知领域,26/02/2019,//www.cpetem.com/2019/02/pydata-montreal-slides-for-the-talk-pytorch-under-the-hood/

188betiosapp

更新2019年2月28日:我添加了一个新的博客文章用幻灯片平台C上taining the presentation I did for PyData Montreal.

今天,在PyTorch开发者大会中,PyTorch队宣布计划与PyTorch 1.0预览有许多不错的释放如模型图一个JIT(有和没有跟踪)功能,以及在LibTorch中,PyTorch C ++ API中的所述一个最重要的发行公告今天在我看来做。

鉴于理解这个新的API是如何工作的,我决定写这篇文章显示的是现在PyTorch C ++ API发布后打开许多机会的一个例子的巨大兴趣。在这篇文章中,我将PyTorch推理整合到一个使用的NodeJS c + +插件,就像不同的框架/语言使用C ++ API,它现在可能的整合的一个例子本土的NodeJS。

Below you can see the final result:

As you can see, the integration is seamless and I could use a traced ResNet as the computational graph model and feed any tensor to it to get the output predictions.

介绍

简单地说,libtorch是PyTorch库版本。它包含的底层基础所使用的PyTorch,如ATEN(张量库),它包含了所有的张量操作和方法。还Libtorch包含autograd,which is the component that adds the automatic differentiation to the ATen tensors.

谨慎为那些谁现在正在开始一个字是要小心使用,可以从ATEN和autograd创建两个张量,不要混合使用它们,the ATen will return the plain tensors (when you create them using the命名空间)而autograd函数(从torch命名空间)将返回变量中,加入其自动分化机制。

有关PyTorch内部工作方式的一个更广泛的教程,请把我以前的上教程一看PyTorch内部架构

Libtorch可以从下载Pytorch网站它仅作为一会儿预览。您还可以找到文档本网站,which is mostly a Doxygen rendered documentation. I found the library pretty stable, and it makes sense because it is actually exposing the stable foundations of PyTorch, however, there are some issues with headers and some minor problems concerning the library organization that you might find while starting working with it (that will hopefully be fixed soon).

对于的NodeJS,我将使用Native Abstractionslibrary (nan) which is the most recommended library (actually is basically a header-only library) to create NodeJS C++ add-ons and theCmake-js,因为libtorch已经提供cmake的文件,使我们的建设过程变得更加容易。然而,这里的重点将是C ++代码,而不是在建设过程中。

为发展方向,跟踪,序列化和加载模型,流程可以在左边的图中可以看出。

它从开发过程和PyTorch(Python的域)追查正在做的,然后在C ++领域的装载和推理(在我们的例子中添加的NodeJS上)。

结束语张量

在的NodeJS,创建一个对象的JavaScript的世界一流的公民,你需要继承自ObjectWrap类,这将负责用于包装C ++组件。

#ifndef TENSOR_H #define TENSOR_H #include  #include  namespace torchjs { class Tensor : public Nan::ObjectWrap { public: static NAN_MODULE_INIT(Init); void setTensor(at::Tensor tensor) { this->mTensor = tensor; } torch::Tensor getTensor() { return this->mTensor; } static v8::Local NewInstance(); private: explicit Tensor(); ~Tensor(); static NAN_METHOD(New); static NAN_METHOD(toString); static Nan::Persistent constructor; private: torch::Tensor mTensor; }; } // namespace torchjs #endif

As you can see, most of the code for the definition of our Tensor class is just boilerplate. The key point here is that thetorchjs ::张量将一个包裹火炬::张量我们增加了两个特殊的公共方法(setTensorgetTensor) to set and get this internal torch tensor.

I won’t show all the implementation details because most parts of it are NodeJS boilerplate code to construct the object, etc. I’ll focus on the parts that touch the libtorch API, like in the code below where we are creating a small textual representation of the tensor to show on JavaScript (的toString方法):

NAN_METHOD(张量::的toString){张量* OBJ = ObjectWrap ::展开<张量>(info.Holder());的std :: stringstream的SS;在:: intList中尺寸= obj-> mTensor.sizes();SS << “张量[类型=” << obj-> mTensor.type()<< “”;SS << “大小=” <<尺寸<<的std :: ENDL;。info.GetReturnValue()设置(楠::新(ss.str())ToLocalChecked());}

What we are doing in the code above, is just getting the internal tensor object from the wrapped object by解缠它。之后,我们建立与张力的大小(各维的尺寸)和它的类型(浮法等)的字符串表示。

Wrapping Tensor-creation operations

Let’s create now a wrapper code for the火炬::者函数负责创建填充有恒定1项的任何定义的形状的张量。

NAN_METHOD(个){//理智的参数检查,如果(info.Length()<2)返回楠:: ThrowError(楠::新( “错误的数目的参数”)ToLocalChecked());如果(!信息[0]  - > IsArray的()||信息[1]  - > IsBoolean()!)返回楠:: ThrowError(楠::新( “错误的参数类型”)ToLocalChecked());//检索参数(require_grad和张量形状)const的布尔require_grad =信息[1]  - > BooleanValue中();常量V8 ::本地阵列=信息[0]。如();常量uint32_t的长度=阵列 - >长度();//从V8 ::数组转换为标准::矢量的std ::矢量<长长>变暗;对于(中间体I = 0;我<长度;我++){V8 ::本地 V;INT d =阵列 - >获取(ⅰ) - > NumberValue();dims.push_back(d);} //调用libtorch并创建一个新torchjs ::张量对象//包装新火炬::张量,是由火炬创建::在::张量V =火炬::一(变暗,火炬:: requires_grad的人(require_grad)); auto newinst = Tensor::NewInstance(); Tensor* obj = Nan::ObjectWrap::Unwrap(newinst); obj->setTensor(v); info.GetReturnValue().Set(newinst); }

所以,让我们通过这个代码。我们首先检查函数的参数。对于这个功能,我们期待为张量形状和指示,如果我们要计算梯度或不是此张量节点的布尔元组(JavaScript数组)。在那之后,我们把从V8的JavaScript类型分为本地C ++类型的参数。一旦我们所需要的参数,我们再调用火炬::者从libtorch功能,这个功能将创建在这里我们使用一个新的张量torchjs ::张量类,我们创建较早把它包起来。

就是这样,我们刚刚曝光,可以用来作为本地JavaScript运行一个火炬操作。

间奏曲为PyTorch JIT

引进的PyTorch JIT围绕着火炬脚本的概念。火炬脚本是Python语言的有限子集,并配有自己的编译器和转换通行证(优化等)。

该脚本可以用两种不同方式创建:通过使用跟踪JIT或通过提供脚本本身。在跟踪模式下,计算图节点将被访问并记录操作以生产最终的脚本,而脚本是你提供你的模型拍摄的描述考虑到火炬脚本的限制模式。

请注意,如果你有你的代码依赖于外部因素或数据,跟踪你希望不会工作,因为它会记录下图的某次执行,因此替代方案,以提供脚本分支决定。然而,在大多数的情况下,跟踪是我们所需要的。

了解这些差异,让我们从两个通过跟踪和脚本生成的脚本模块来看看中间表示(IR)。

@ torch.jit.script DEF happy_function_script(X):RET = torch.rand(0)如果真== TRUE:RET = torch.rand(1)否则:RET = torch.rand(2)返回RET DEF happy_function_trace(X):t RET = torch.rand(0)如果真== TRUE:RET = torch.rand(1)否则:RET = torch.rand(2)返回RET traced_fn = torch.jit.trace(happy_function_trace,(torch.tensor(0),),check_trace = FALSE)

在上面的代码中,我们提供两个功能,一个是使用@ torch.jit.script装饰,并且它是脚本的方式来创建一个火炬脚本,而第二个功能是正在使用的跟踪功能torch.jit.trace。这并不是说我故意增加了一个“真正的==真”的功能(这将永远是正确的)决定。

没有w, if we inspect the IR generated by these two different approaches, we’ll clearly see the difference between the tracing and scripting approaches:

#1)格拉夫从编写脚本的方法图(%×:动态){%16:整数=拘谨::常数[值= 2]()%10:整数=拘谨::常数[值= 1]()%7:INT =拘谨::常数[值= 1]()%8:整数=拘谨::常数[值= 1]()%9:整数= ATEN ::当量(%7,%8)%保留:动态= ::拘谨如果(%9)BLOCK0(){%11:INT [] =拘谨:: ListConstruct(%10)%12:整数=拘谨::常数[值= 6]()%13:INT =拘谨::常数[值= 0]()%14:INT [] =拘谨::常数[值= [0,-1]()%ret.2:动态=阿坦::兰特(11%,12%,%13, %14) -> (%ret.2) } block1() { %17 : int[] = prim::ListConstruct(%16) %18 : int = prim::Constant[value=6]() %19 : int = prim::Constant[value=0]() %20 : int[] = prim::Constant[value=[0, -1]]() %ret.3 : Dynamic = aten::rand(%17, %18, %19, %20) -> (%ret.3) } return (%ret); } # 2) Graph from the tracing approach graph(%0 : Long()) { %7 : int = prim::Constant[value=1]() %8 : int[] = prim::ListConstruct(%7) %9 : int = prim::Constant[value=6]() %10 : int = prim::Constant[value=0]() %11 : int[] = prim::Constant[value=[0, -1]]() %12 : Float(1) = aten::rand(%8, %9, %10, %11) return (%12); }

正如我们所看到的,IR是非常相似的亚洲金博宝LLVM IR应注意,在跟踪方法,跟踪记录包含的代码,真理的路径只有一条路径,而在脚本我们既有分支的替代品。然而,即使在脚本中,总是假的分支可以进行优化,并与死代码消除变换通过去除。

PyTorch JIT有很多被用来做循环展开改造通行证,死代码消除等。您可以找到这些的passes here。不是转换成其它格式,例如ONNX可以被实施为在该中间表示(IR),这是相当方便的顶部一通。

追根RESNET

现在,在执行的NodeJS的脚本模块之前,让我们先跟踪使用PyTorch(只使用Python)的一个RESNET网络:

traced_net = torch.jit.trace(torchvision.models.resnet18(),torch.rand(1,3,224,224))traced_net.save( “resnet18_trace.pt”)

正如可以从上面的代码中看到的,我们只需要提供一个张量的例子(在此情况下一个批次的单个图像的与3个通道和尺寸224×224。之后,我们只需保存追踪网络到一个文件称为resnet18_trace.pt

没有w we’re ready to implement the Script Module in NodeJS in order to load this file that was traced.

包裹脚本模块

这是现在的脚本模块中的的NodeJS执行:

// Class构造ScriptModule :: ScriptModule(常量的std :: string文件名){//加载从文件这 - > mModule =炬:: JIT ::负载(文件名)所追踪的网络;} // JavaScript对象创建NAN_METHOD(ScriptModule ::新){如果(info.IsConstructCall()){//获取文件名参数V8 ::字符串:: Utf8Value param_filename(信息[0]  - >的ToString());常量的std :: string文件名=的std :: string(* param_filename);//使用该文件名ScriptModule * OBJ =新ScriptModule(文件名)创建一个新的脚本模块;obj->裹(info.This());。info.GetReturnValue()设置(info.This());}否则{V8 ::本地缺点=楠::新(构造);。info.GetReturnValue()设置(楠:: NewInstance方法(缺点).ToLocalChecked());}}

正如你可以从上面的代码中看到,我们只是创建一个类,将调用火炬:: JIT ::负荷功能经过追查网络的文件名。我们也有JavaScript对象,我们在那里参数转换为C ++类,然后创建的一个新实例的执行torchjs :: ScriptModule

直传的包装也很简单:

NAN_METHOD(ScriptModule ::向前){ScriptModule * script_module = ObjectWrap ::展开(info.Holder());楠:: MaybeLocal 说不定=楠::要(信息[0]);张量*张量=楠:: ObjectWrap ::展开<张量>(maybe.ToLocalChecked());火炬::张量torch_tensor = tensor-> getTensor();火炬::张量输出= script_module-> mModule->向前({torch_tensor})toTensor();自动newinst中=张量:: NewInstance方法();张量* OBJ =楠:: ObjectWrap ::展开<张量>(newinst中);obj-> setTensor(输出);。info.GetReturnValue()设置(newinst中);}

正如你所看到的,在这段代码中,我们只收到张量作为参数,我们得到的内部火炬::张量从它,然后调用从脚本模块forward方法,我们总结了新的输出torchjs ::张量然后返回。

And that’s it, we’re ready to use our built module in native NodeJS as in the example below:

var torchjs =要求(“。/构建/发布/ torchjs”);var script_module = new torchjs.ScriptModule("resnet18_trace.pt"); var data = torchjs.ones([1, 3, 224, 224], false); var output = script_module.forward(data);

我希望你喜欢!Libtorch打开了在许多不同的语言和框架的紧密集成PyTorch,这是非常令人兴奋,对生产部署代码的方向迈出了一大步门。

- 基督教S. Perone

Cite this article as: Christian S. Perone, "PyTorch 1.0 tracing JIT and LibTorch C++ API to integrate PyTorch into NodeJS," in亚洲金博宝未知领域,2018年2月10日,188betiosapp

JIT生成本机代码使用Python和LLVM TensorFlow计算图

更新:黑客新闻这里讨论

The TensorFlow Computation Graph

tensorlogo

其中一个最惊人的组件TensorFlow架构是计算图可以使用被序列协议缓冲。此计算图遵循良好定义的格式(点击这里for the proto files) and describes the computation that you specify (it can be a Deep Learning model like a CNN, a simple Logistic Regression or even any computation you want). For instance, here is an example of a very simple TensorFlow computation graph that we will use in this tutorial (using TensorFlow Python API):

进口tensorflow与tf.Session()作为编码扩频通信TF:input_placeholder = tf.placeholder(tf.int32,1,名称= “输入”)sub_op = tf.sub(input_placeholder,tf.constant(2,D型= tf.int32))add_op = tf.add(sub_op,tf.constant(5,D型细胞= tf.int32))输出= tf.add(add_op,tf.constant(100,D型细胞= tf.int32),名称= “输出”)tf.train.write_graph(sess.graph_def, “”, “graph.pb”,真)
计算图的表示。
计算图的表示。

正如你所看到的,这是一个非常简单的计算图表。亚洲金博宝首先,我们定义将一直保持输入张量的占位符,并且我们指定应该发生的使用该输入张量作为输入数据的计算后。在这里我们也可以看到,我们定义这个图的两个重要的节点,一个被称为“输入”(上述的占位符),而另一个被称为“产量“,将举行最后的计算结果。该曲线图是一样的下面的公式一个标量:输出=(((输入 -  2)-5)100),其中I有意添加的冗余操作,以在后面看到LLVM恒定传播。

在代码的最后一行,我们坚持这个计算图(包括常量值)成序列化的protobuf文件。最后真正参数是输出的文本表示来代替二进制,所以它会产生以下人类可读输出protobuf的文件(I为了简洁省略它的一部分):

节点{名: “输入” OP “占位符” ATTR {键: “D型” 值{类型:DT_INT32}} ATTR {键: “形状” 值​​{形状{暗淡{大小:1}}}}}节点{名: “常量” 运算: “常量” ATTR {键: “D型” 值{类型:DT_INT32}} {ATTR键: “值” 的值{张量{D型细胞:DT_INT32 tensor_shape {} int_val:2}}}} --->(为简洁起见省略)<---节点{名: “输出” OP: “添加” 输入: “添加” 输入: “Const_2” ATTR {键: “T” 值{类型:DT_INT32}}} {版本生产者:9}

这是一个非常简单的图亚洲金博宝表,图表TensorFlow是其实从来没有这么简单,because TensorFlow models can easily contain more than 300 nodes depending on the model you’re specifying, specially for Deep Learning models.

我们将使用上图展示了如何我们可以为这个简单的图形使用JIT本机代码LLVM框架

LLVM的前端,IR和后端

LLVM-LOGO的微分1

The LLVM framework is a really nice, modular and complete ecosystem for building compilers and toolchains. A very nice description of the LLVM architecture that is important for us is shown in the picture below:

LLVM编译器架构
LLVM编译器架构(AOSA / LLVM,克里斯·拉特纳)

(The picture above is just a small part of the LLVM architecture, for a comprehensive description of it, pleasesee the nice article由克里斯·拉特纳写的书AOSA)

Looking in the image above, we can see that LLVM provides a lot of core functionality, in the left side you see that many languages can write code for their respective language frontends, after that it doesn’t matter in which language you wrote your code, everything is transformed into a very powerful language calledLLVM IRLLVM中间表示) which is as you can imagine, a intermediate representation of the code just before the assembly code itself. In my opinion, the IR is the key component of what makes LLVM so amazing, because it doesn’t matter in which language you wrote your code (or even if it was a JIT’ed IR), everything ends in the same representation, and then here is where the magic happens, because the IR can take advantage of the LLVM optimizations (also known as变换和分析通行证)。

After this IR generation, you can feed it into any LLVM backend to generate native code for any architecture supported by LLVM (such as x86, ARM, PPC, etc) and then you can finally execute your code with the native performance and also after LLVM optimization passes.

在使用LLVM为了JIT代码,所有你需要的是编程建立IR,创建一个执行引擎转换(在执行时间)的IR为本地代码,获取你有JIT'ed,最后执行它的函数的指针。我会在这里使用的Python LLVM结合称为llvmlite,which is very Pythonic and easy to use.

JIT’ing TensorFlow Graph using Python and LLVM

流

现在,让我们使用LLVM和Python进行JIT的TensorFlow计算图表。这是绝不是全面贯彻实施,这是非常简单的亚洲金博宝方法,即假设某些事情过于简单化:一个整数闭合类型,只是一些TensorFlow操作,也是一个单个标量的支持,而不是高秩张量。

所以,让我们开始建立我们的JIT代码;首先,让我们来导入所需的包,初始化一些LLVM子系统,也定义了TensorFlow整数类型的LLVM各自类型:

从进口的ctypes CFUNCTYPE,c_int的进口tensorflow作为从tensorflow.core.framework进口graph_pb2 google.protobuf进口TEXT_FORMAT从tensorflow.core.framework进口types_pb2从tensorflow.python.framework进口OPS TF导入llvmlite.ir为L1进口llvmlite.binding如LLVM llvm.initialize()llvm.initialize_native_target()llvm.initialize_native_asmprinter()TYPE_TF_LLVM = {types_pb2.DT_INT32:ll.IntType(32),}

在此之后,让我们来定义一个类来打开TensorFlow出口图形,也宣告一个方法来获得由名图的节点:

类TFGraph(对象)的:def __init __(个体,文件名= “graph.pb”,二进制=假):self.graph_def = graph_pb2.GraphDef()与打开( “graph.pb”, “RB”)为f:如果二进制:self.graph_def.ParseFromString(f.read())其他:text_format.Merge(f.read(),self.graph_def)高清get_node(个体经营,名):为节点self.graph_def.node:如果节点。命名==名称:返回节点

让我们先来定义我们的主要功能将是代码的起点开始:

DEF run_main():图表= TFGraph( “graph.pb”,假)input_node = graph.get_node( “输入”)output_node = graph.get_node( “输出”)INPUT_TYPE = TYPE_TF_LLVM [input_node.attr [ “D型”]。型] output_type = TYPE_TF_LLVM [output_node.attr [ “T”]。式]模块= ll.Module()func_type = ll.FunctionType(output_type,[INPUT_TYPE])FUNC = ll.Function(模块,func_type,名称='tensorflow_graph')func.args [0]。名称= '输入' bb_entry = func.append_basic_block(' 条目')ir_builder = ll.IRBuilder(bb_entry)

As you can see in the code above, we open the serialized protobuf graph and then get the input and output nodes of this graph. After that we also map the type of the both graph nodes (input/output) to the LLVM type (from TensorFlow integer to LLVM integer). We start then by defining a LLVM Module, which is the top level container for all IR objects. One module in LLVM can contain many different functions, here we will create just one function that will represent the graph, this function will receive as input argument the input data of the same type of the input node and then it will return a value with the same type of the output node.

在此之后,我们通过创建函数的入口块并使用该块我们实例IR生成器,这是一个对象,将提供给我们的构建块TensorFlow图形JIT'ing操作开始。

现在让我们来定义,会做转换TensorFlow节点成LLVM IR的实际工作中的作用:

DEF build_graph(ir_builder,图形,节点):如果node.op == “添加”:left_op_node = graph.get_node(node.input [0])= right_op_node graph.get_node(node.input [1])= left_op build_graph(ir_builder,图形,left_op_node)right_op = build_graph(ir_builder,图形,right_op_node)返回ir_builder.add(left_op,right_op)如果node.op == “子”:left_op_node = graph.get_node(node.input [0])= right_op_nodegraph.get_node(node.input [1])= left_op build_graph(ir_builder,图形,left_op_node)right_op = build_graph(ir_builder,图形,right_op_node)返回ir_builder.sub(left_op,right_op)如果node.op == “占位”:function_args =在function_args为ARG ir_builder.function.args:如果arg.name == node.name:回报ARG提高RuntimeError( “!输入[{}]未找到” 格式(node.name))如果node.op == “常量”:llvm_const_type = TYPE_TF_LLVM [node.attr [ “D型”]键入] const_value = node.attr [ “值”] tensor.int_val [0] = llvm_const_value llvm_const_type(const_value)返回llvm_const_value

在这个函数中,我们接收的参数IR生成器,我们之前创建的图形类和输出节点。该函数将递归通过IR生成器来构建LLVM IR。在这里你可以看到,我只能从TensorFlow图实施的加/减/占位符和const操作,只是为了能够支持我们先前定义的图表。

在那之后,我们只需要定义将采取LLVM模块,然后创建将做转换IR成原生x86代码的辛勤工作之前执行过LLVM IR的LLVM优化执行引擎的功能:

DEF create_engine(模块):特征= llvm.get_host_cpu_features()变平()llvm_module = llvm.parse_assembly(STR(模块))目标= llvm.Target.from_default_triple()target_machine = target.create_target_machine(优化= 3,设有=特征)发动机= llvm.create_mcjit_compiler(llvm_module,target_machine)engine.finalize_object()打印target_machine.emit_assembly(llvm_module)返回发动机

在上面的代码,你可以看到,我们首先得到的CPU功能(SSE等)到一个列表,之后我们从模块解析LLVM IR,然后我们创建一个使用最大优化级别发动机(选择= 3,大致equivalent to the GCC -O3 parameter), we’re also printing the assembly code (in my case, the x86 assembly built by LLVM).

And here we just finish ourrun_main()功能:

RET = build_graph(ir_builder,图形,output_node)ir_builder.ret(RET)与打开( “output.ir”, “W”)为f:f.write(STR(模块))发动机= create_engine(模块)func_ptr =发动机.get_function_address( “tensorflow_graph”)cfunc = CFUNCTYPE(c_int的,c_int的)(func_ptr)RET = cfunc(10)打印 “执行输出:{}”。格式(RET)

正如你可以在上面的代码中看到,我们只是调用build_graph()方法然后使用IR生成器添加的“RET” LLVM IR指令(RET =返程)返回刚刚创建基于所述TensorFlow图表的IR函数的输出。我们也在这里写IR输出到外部文件,我将在后面使用这个LLVM IR文件为其他不同的体系结构,如ARM架构创建本地组装。最后,只得到本机代码的函数地址,创建一个Python包装了这个功能,然后用参数“10”,这将是输入数据,然后输出生成的输出值调用它。

这就是它,当然,这仅仅是一个过于简单化,但现在我们知道有我们TensorFlow车型JIT的优势。

输出LLVM IR,优化和多个体系结构的优点(ARM,PPC,86等)

For instance, lets create the LLVM IR (using the code I shown above) of the following TensorFlow graph:

进口tensorflow与tf.Session()作为编码扩频通信TF:input_placeholder = tf.placeholder(tf.int32,1,名称= “输入”)sub_op = tf.sub(input_placeholder,tf.constant(2,D型= tf.int32))add_op = tf.add(sub_op,tf.constant(5,D型细胞= tf.int32))输出= tf.add(add_op,tf.constant(100,D型细胞= tf.int32),名称= “输出”)tf.train.write_graph(sess.graph_def, “”, “graph.pb”,真)

产生的LLVM IR是下面这一个:

;的moduleId = “” 目标三重= “未知未知未知” 目标datalayout = “” 定义I32 @ “tensorflow_graph”(I32% “输入”){条目:” 3" (%)=子I32% “输入”,2%“0.4" =加I32%” 3" ,5% “5" =加I32%” 4" ,100保留123-132%”。5" }

正如你所看到的,LLVM IR看起来很像一个汇编代码,但这不是最后的汇编代码,这只是一个非优化的IR呢。刚生成的x86汇编代码之前,LLVM运行了很大的优化越过LLVM IR,它会做的事情,如死代码消除,常量传播,等等。这里是最后的原生x86汇编代码LLVM生成的所述TensorFlow图的上面LLVM IR:

的.text .file “<字符串>” .globl tensorflow_graph .align伪16,0×90 .TYPE tensorflow_graph,@功能tensorflow_graph:.cfi_startproc利尔103(%RDI),%eax中retq .Lfunc_end0:.size tensorflow_graph,.Lfunc_end0-tensorflow_graph .cfi_endproc.section伪 “.note.GNU栈”, “”,@ PROGBITS

正如你所看到的,优化的代码删除了很多冗余的操作,并最终只是做的103加法运算,这是我们在图中所定义的计算正确的简化。对于大图,你可以看到,这些优化可真厉害,因为我们重用Compiler optimizations that were developed for years在我们的机器学习模型计算

您还可以使用所谓的“有限责任公司”一LLVM工具,可以采取一个LLVM IR文件和生成汇编为你需要的任何其他平台,例如,下面的命令行会为ARM架构的本机代码:

LLC -O3 out.ll -march =手臂-o sample.s

The outputsample.s文件是以下之一:

的.text .syntax统一.eabi_attribute 67, “2.09” @ Tag_conformance .eabi_attribute 6,1 @ Tag_CPU_arch .eabi_attribute 8,1 @ Tag_ARM_ISA_use .eabi_attribute 17,1 @ Tag_ABI_PCS_GOT_use .eabi_attribute 20,1 @ Tag_ABI_FP_denormal .eabi_attribute 21,1 @ Tag_ABI_FP_exceptions。eabi_attribute 23,3 @ Tag_ABI_FP_number_model .eabi_attribute 34,1 @ Tag_CPU_unaligned_access .eabi_attribute 24,1 @ Tag_ABI_align_needed .eabi_attribute 25,1 @ Tag_ABI_align_preserved .eabi_attribute 38,1 @ Tag_ABI_FP_16bit_format .eabi_attribute 14,0 @ Tag_ABI_PCS_R9_use .file “out.ll” .globltensorflow_graph .align伪2 .TYPE tensorflow_graph,%功能tensorflow_graph:@ @tensorflow_graph .fnstart @ BB#0:@%条目添加R0,R0,#103 MOV PC,LR .Lfunc_end0:.size tensorflow_graph,.Lfunc_end0-tensorflow_graph .fnend。部 “.note.GNU堆叠”, “”,%PROGBITS

As you can see above, the ARM assembly code is also just a “add” assembly instruction followed by a return instruction.

这是非常好的,因为我们可以采取LLVM架构的天然优势。举例来说,今天的ARM刚刚宣布的ARMv8-A具有可伸缩矢量扩展(SVE)that will support2048位矢量,他们是已经工作补丁LLVM。在未来,一个非常好的除了LLVM将LLVM开发通行证进行分析和改造,将考虑到的机器学习模型的性质。

就是这样,我希望你喜欢的文章!是真的真棒,你可以使用Python,LLVM和TensorFlow几行做什么。

更新2016年8月22日乔什克朗茨只是指出他酷的项目Called Likely on黑客新闻的讨论

更新2016年8月22日:TensorFlow团队实际上是工作的一个JIT(如果他们使用LLVM我不知道,但似乎最合理的方式在我看来去)。在他们的论文,也有对未来的工作,我在这里举一个非亚洲金博宝常重要的声明:

“我们也有一些具体的方向,提高TensorFlow的性能。这样的一个方向,是我们在刚刚即时编译器,可以采取一个TensorFlow执行的子图,也许关于张量的典型尺寸和形状的一些运行时分析信息,并可以生成此子优化的常规前期工作。这个编译器会明白执行多个优化,如环融合的,阻断和平铺为局部性,专业化为特定的形状和大小等的语义”-TensorFlow白皮书

Full code

从进口的ctypes CFUNCTYPE,c_int的进口tensorflow作为从tensorflow.core.framework进口graph_pb2 google.protobuf进口TEXT_FORMAT从tensorflow.core.framework进口types_pb2从tensorflow.python.framework进口OPS TF导入llvmlite.ir为L1进口llvmlite.binding如LLVM llvm.initialize()llvm.initialize_native_target()llvm.initialize_native_asmprinter()TYPE_TF_LLVM = {types_pb2.DT_INT32:ll.IntType(32),}类TFGraph(对象)的:def __init __(个体,文件名= “graph.pb”,二进制=假):self.graph_def = graph_pb2.GraphDef()与打开( “graph.pb”, “RB”)为f:如果二进制:self.graph_def.ParseFromString(f.read())其他:text_format.Merge(f.read(),self.graph_def)高清get_node(个体经营,名):为节点self.graph_def.node:如果节点。命名==名称:返回节点DEF build_graph(ir_builder,图形,节点):如果node.op == “添加”:left_op_node = graph.get_node(node.input [0])= right_op_node graph.get_node(node.input [1])= left_op build_graph(ir_builder,图形,left_op_node)right_op = build_graph(ir_builder,图形,right_op_node)返回ir_builder.add(left_op,right_op)如果node.op == “子”:left_op_node = graph.get_node(node.input [0])= right_op_nodegraph.get_node(node.input [1])= left_op build_graph(ir_builder,图形,left_op_node)right_op = build_graph(ir_builder,图形,right_op_node)返回ir_builder.sub(left_op,right_op)如果node.op == “占位”:function_args =在function_args为ARG ir_builder.function.args:如果arg.name == node.name:回报ARG提高RuntimeError( “!输入[{}]未找到” 格式(node.name))如果node.op == “常量”:llvm_const_type = TYPE_TF_LLVM [node.attr [ “D型”]键入] const_value = node.attr [ “值”] tensor.int_val [0] = llvm_const_value llvm_const_type(const_value)返回llvm_const_valueDEF create_engine(模块):特征= llvm.get_host_cpu_features()变平()llvm_module = llvm.parse_assembly(STR(模块))目标= llvm.Target.from_default_triple()target_machine = target.create_target_machine(优化= 3,设有=特征)发动机= llvm.create_mcjit_compiler(llvm_module,target_machine)engine.finalize_object()打印target_machine.emit_assembly(llvm_module)返回发动机 def run_main(): graph = TFGraph("graph.pb", False) input_node = graph.get_node("input") output_node = graph.get_node("output") input_type = TYPE_TF_LLVM[input_node.attr["dtype"].type] output_type = TYPE_TF_LLVM[output_node.attr["T"].type] module = ll.Module() func_type = ll.FunctionType(output_type, [input_type]) func = ll.Function(module, func_type, name='tensorflow_graph') func.args[0].name = 'input' bb_entry = func.append_basic_block('entry') ir_builder = ll.IRBuilder(bb_entry) ret = build_graph(ir_builder, graph, output_node) ir_builder.ret(ret) with open("output.ir", "w") as f: f.write(str(module)) engine = create_engine(module) func_ptr = engine.get_function_address("tensorflow_graph") cfunc = CFUNCTYPE(c_int, c_int)(func_ptr) ret = cfunc(10) print "Execution output: {}".format(ret) if __name__ == "__main__": run_main()
引用本文为:基督教S. Perone,“JIT生成本机代码使用Python和LLVM TensorFlow计算图形,”在亚洲金博宝未知领域,22/08/2016,//www.cpetem.com/2016/08/jit-native-code-generation-for-tensorflow-computation-graphs-using-python-and-llvm/

遗传编程和Python的限制AST表达式LLVM JIT

关于理由一个小前奏

所以,我正在写在C / C象征主义回归机++叫闪耀,其目的是对遗传编程文库的JIT(像Pyevolve例如)。The main rationale behind Shine is that we have today a lot of research on speeding Genetic Programming using GPUs (the GPU fever !) or any other special hardware, etc, however we don’t have many papers talking about optimizing GP using the state of art compilers optimizations like we have on clang, gcc, etc.

“热点”或消耗大量的CPU资源的今天遗传编程是每一个人的评价,以计算程序树健身的组件。这种评价往往是在每一组的“培训”的参数设置执行。假设你想的一样勾股定理一个表达式的符号回归,你有1.0的参数的线性空间,以1000.0以0.1步你有你的人口的每一个人(程序树)10.000的评价!

什么服务所做的是下面的图片描述:

它采用遗传编程发动机的个体,然后将其转换为LLVM中间表示(LLVM汇编语言),之后它运行的改造经过LLVM的(这里是现代编译器的真正力量在GP背景下进入),然后将LLVM JIT优化的LLVM IR转换为本地代码的指定目标(X86和PowerPC等)。

You can see below the Shine architecture:

这种架构带来了遗传规划了很大的灵活性,可以为可能后来由LLVM支持的任何语言你的个人使用情况下写的功能,哪些事项服务是LLVM IR,你可以使用任何语言,LLVM支持然后使用由LLVM产生的IR,可以从C,C ++,Ada的,FORTRAN,d,等混合代码,并使用自己的函数作为遗传规划树的非末端节点。

闪耀is still on its earlier development, it looks a simple idea but I still have a lot of problems to solve, things like how to JIT the evaluation process itself instead of doing calls from Python using ctypes bindings of the JITed trees.

对Python的AST本身做遗传编程

在服务的发展,一个想法发生在我身上,我可以使用Python的限制抽象语法树(AST)作为一个遗传编程引擎个人表示,这样做的主要优点是灵活性和重用了很多东西的可能性。Of course that a shared library written in C/C++ would be useful for a lot of Genetic Programming engines that doesn’t uses Python, but since my spare time to work on this is becoming more and more rare I started to rethink the approach and use Python and the LLVM bindings for LLVM (LLVMPY),我才发现,原来是很容易使用JIT LLVM一组有限的Python的AST的本地代码,而这也正是这篇文章将会显现。

JIT’ing a restricted Python AST

LLVM的最惊人的部分显然是经过改造,所述JIT的量,当然通过一个简单的API使用整个框架的能力(确定,不是那么简单有时)。为了简化这个例子中,我将使用任意的限制AST集合了Python AST仅支持减( - ),加(+),乘(*)和除法(/)。

要了解Python的AST,你可以使用Python解析器,转换成源AST:

>>>进口AST >>> ASTP = ast.parse( “2 * 7”)>>> ast.dump(ASTP)“模块(体= [Expr的(值= BinOp(左= NUM​​(N = 2),OP = MULT(),右= NUM​​(N = 7)))])”

What the parse created was an Abstract Syntax Tree containing theBinOp二元运算)与左操作者数目2时,右操作者如数字7和操作本身作为乘法(Mult), very easy to understand. What we are going to do to create the LLVM IR is to create a visitor that is going to visit each node of the tree. To do that, we can subclass the PythonNodeVisitor从类ast模块。What the NodeVisitor does is to visit each node of the tree and then call the method ‘visit_OPERATOR’ if it exists, when the NodeVisitor is going to visit the node for the BinOp for example, it will call the method ‘visit_BinOp’ passing as parameter the BinOp node itself.

在类的JIT游客将看起来像下面的代码的结构:

# Import the ast and the llvm Python bindings import ast from llvm import * from llvm.core import * from llvm.ee import * import llvm.passes as lp class AstJit(ast.NodeVisitor): def __init__(self): pass

我们现在需要做的是创建一个初始化的方法来保持,这是需要的JIT访问者的最后一个状态,因为我们要JIT Python的AST成一个功能的内容和功能需要返回的最后一个指令什么是由JIT访问的最后一个指令的结果。We also need to receive a LLVM Module object in which our function will be created as well the closure type, for the sake of simplicity I’m not type any object, I’m just assuming that all numbers from the expression are integers, so the closure type will be the LLVM integer type.

def __init__(自我、模块、参数):self.last_state = None self.module = module # Parameters that will be created on the IR function self.parameters = parameters self.closure_type = Type.int() # An attribute to hold a link to the created function # so we can use it to JIT later self.func_obj = None self._create_builder() def _create_builder(self): # How many parameters of integer type params = [self.closure_type] * len(self.parameters) # The prototype of the function, returning a integer # and receiving the integer parameters ty_func = Type.function(self.closure_type, params) # Add the function to the module with the name 'func_ast_jit' self.func_obj = self.module.add_function(ty_func, 'func_ast_jit') # Create an argument in the function for each parameter specified for index, pname in enumerate(self.parameters): self.func_obj.args[index].name = pname # Create a basic block and the builder bb = self.func_obj.append_basic_block("entry") self.builder = Builder.new(bb)

现在,我们需要对我们的客人实行什么是对的“visit_OPERATOR”方法BinOp并为Name运营商。我们还将实施创造了返回指令,将返回的最后状态的方法。

#A“名称”是在AST生产时访问#变量,比如“2 + X + Y”,“X”和“y”是#对AST为表达式创建的两个名称的节点。高清visit_Name(个体经营,节点):#这个变量就是函数的参数?指数= self.parameters.index(node.id)self.last_state = self.func_obj.args [指数]返回self.last_state#这里我们创建一个LLVM IR整数常量使用#货号节点,在表达式“2 + 3“你有两个#民节点上,NUM(N = 2)和民(N = 3)。高清visit_Num(个体经营,节点):self.last_state = Constant.int(self.closure_type,node.n)返回self.last_state#为DEF visit_BinOp二元运算访问者(自我,节点):#获取操作,左,右参数LHS = self.visit(node.left)RHS = self.visit(node.right)OP = node.op#转换每个操作(子,添加,MULT,DIV)到其#LLVM IR整数指令等效如果isinstance(OP,ast.Sub):OP = self.builder.sub(左,右轴, 'sub_t')的elif isinstance(OP,ast.Add):OP = self.builder.add(左,右轴, 'add_t')elif的isinstance(OP,ast.Mult):OP = self.builder.mul(左,右轴, 'mul_t')的elif isinstance(OP,ast.Div):OP = self.builder.sdiv(左,右轴,“sdiv_t“)self.last_state =运回self.last_state#建立与过去的状态返回(RET)语句高清build_return(个体经营):self.builder.ret(self.last_state)

这就是它,我们的客人准备一个Python AST转换为LLVM IR汇编语言编写,运行它,我们将首先创建一个LLVM模块和一个表达式:

模块= Module.new( 'ast_jit_module')#请注意,我使用两个变量 'A' 和 'b' EXPR =“(2 + 3 * B + 33 *(10/2)+ 1 + 3/3 +一)/ 2" 节点= ast.parse(表达式)打印ast.dump(节点)

将输出:

模块(体= [Expr的(值= BinOp(左= BinOp(左= BinOp(左= BinOp(左= BinOp(左= BinOp(左= NUM​​(N = 2),OP =添加(),右= BinOp(左= NUM​​(N = 3),OP = MULT(),右=名称(ID = 'b',CTX =负载()))),OP =添加(),右= BinOp(左= NUM​​(N =33),OP = MULT(),右= NUM​​(N = 2))),OP =添加(),右= NUM​​(N = 1)),OP =添加(),右= NUM​​(N = 3)),OP =添加(),右=名称(ID = 'A',CT​​X =负载())),OP =股利(),右= NUM​​(N = 2)))])

现在,我们终于可以对生成AST运行我们的访问者检查LLVM IR输出:

访问者= AstJit(模块,[ '一', 'B'])visitor.visit(节点)visitor.build_return()打印模块

将输出LLVM IR:

;ModuleID = 'ast_jit_module' define i32 @func_ast_jit(i32 %a, i32 %b) { entry: %mul_t = mul i32 3, %b %add_t = add i32 2, %mul_t %add_t1 = add i32 %add_t, 165 %add_t2 = add i32 %add_t1, 1 %add_t3 = add i32 %add_t2, 1 %add_t4 = add i32 %add_t3, %a %sdiv_t = sdiv i32 %add_t4, 2 ret i32 %sdiv_t }

现在是真正的乐趣开始的时候,我们要运行LLVM优化过程具有同等GCC -02优化级别来优化我们的代码,要做到这一点,我们创建一个PassManagerBuilder和PassManager的PassManagerBuilder是增加了通行证组件PassManager,您也可以手动添加像死代码消除,内联函数等任意的变换:

PMB = lp.PassManagerBuilder.new()#优化级别pmb.opt_level =下午2点= lp.PassManager.new()pmb.populate(下午)#执行通入模块pm.run(模块)的打印模块

将输出:

;ModuleID = 'ast_jit_module' define i32 @func_ast_jit(i32 %a, i32 %b) nounwind readnone { entry: %mul_t = mul i32 %b, 3 %add_t3 = add i32 %a, 169 %add_t4 = add i32 %add_t3, %mul_t %sdiv_t = sdiv i32 %add_t4, 2 ret i32 %sdiv_t }

在这里,我们拥有了Python AST表达的优化的LLVM IR。下一步骤是将其JIT IR为本地代码,然后与一些参数执行它:

ee = ExecutionEngine.new(module) arg_a = GenericValue.int(Type.int(), 100) arg_b = GenericValue.int(Type.int(), 42) retval = ee.run_function(visitor.func_obj, [arg_a, arg_b]) print "Return: %d" % retval.as_int()

将输出:

Return: 197

就是这样,你已经创建了一个AST-> LLVM IR转换器,优化了LLVM IR与改造通行证,然后使用LLVM执行引擎它转换为本地代码。我希望你喜欢=)

引用本文为:基督教S. Perone,“遗传编程和Python的限制AST表达式LLVM JIT,”在亚洲金博宝未知领域,15/08/2012,//www.cpetem.com/2012/08/genetic-programming-and-a-llvm-jit-for-restricted-python-ast-expressions/

未来可以在RPython现在写

最近的一篇文章为什么争论PyPy是Python的未来,我必须说,PyPy是不是Python的未来,是本。当我已经测试它最后一次(PyPy-C 1.1.0)与Pyevolve成一个简单的球函数的优化,它比空载燕子Q2慢至少2倍,但在那个时候,PyPy无法JIT。现在,随着PyPy的新版本和JIT'ing支持,该方案已经改变。

PyPy已经发展了很多(实际上,你可以看到这这里的演变), 一个好工作在GC系统上完成,节省了(相对于CPython的时候),每个对象的8个字节分配,这对于大量使用对象分配(GP系统是这方面的一个强有力的例子,因为当它们在目标实现的应用非常有趣亚洲金博宝取向的语言,各语法树节点是一个对象)。工作也正在进行以改善CPython的扩展的支持(用C / C ++),它们中的一个是有些复杂:利用RPyC的,到代理通过TCP远程调用CPython的;但对方似乎远远更有效,这是对创作CPyExt子系统。By using CPyExt, all you need is to have your CPython API functions implemented in CPyExt, a lot of people is working on this right now and you can do it too, it’s a long road to have a good API coverage, but when you think about advantages, this road becomes small.

为了基准CPython的,Jython的,CPython的+ Psyco是,空载燕子和PyPy,我已经使用了Rastrigin function优化(即实施的例子是在这里Example 7of Pyevolve 0.6rc1):

f(x) = 10n + \sum_{i=1}^{n}{x_{i}^{2}}-10 \ COS(2 \ PI X_ {I})

由于其较大的空间搜索和局部极小的数量,Rastrigin功能通常用来衡量遗传算法的性能。Rastrigin函数在全球最低x = 0的其中,F(X)= 0;为了增加搜索空间和所需的资源,我用40个变量(N = 40)和10K代。

以下是有关在这个基准测试中使用的版本信息:

没有暖身在JVM或PyPy进行。PyPy翻译用的是“-Ojit”选项,以获得Python解释器的JIT版本执行。The JVM was executed using the server mode, I’ve tested the client and server mode for Sun JVM and IcedTea6, the best results were observed from the server mode using Sun JVM, however when I’ve compared the client mode of IcedTea6 with the client mode of Sun JVM, the best results observed were from IcedTea6 (the same as using server mode in IcedTea6). Unladen Swallow was compiled using the project维基指令建立优化的二进制文件。

The machine used was an Intel(R) Core(TM) 2 Duo E4500 (2×2.20Ghz) with 2GB of RAM.

基准的结果在每个设置秒(这些结果是最好的3个连续运行的)(使用壁时间测定):

正如你可以看到,相比于CPython的2.6.5和2.0倍时相比,空载电流燕子更快行李箱与PyPy JIT得到了2.57x的加速。

PyPy不仅是Python的未来,但现在已成为本。PyPy不会给我们带来的不仅在Python的Python实现(这本身就是有价值的结果efforts), but also will bring the performance back (which many doubted at the beginning, wondering how could it be possible for an implementation of Python in Python be faster than an implementation in C ? And here is where the translation and JIT magic enters). When the time comes that Python interpreter can be entire written in a high level language (actually almost the same language, which is really weird), Python community can put their focus on improving the language itself instead of spending time solving the complexity of the lower level languages, is this not the great point of those efforts ?

By the way, just to note, PyPy isn’t only a translator for the Python interpreter written in RPython, it’s a translator of RPython, what means that PyPy isn’t only the future of Python, but probably, the future of many interpreters.

一种用于JIT'ing算法和数据结构与LLVM方法

llvm_dragon

Hello folks, I always post about Python and EvoComp (Pyevolve), but this time it’s about C,LLVM,搜索算法和数据结构。这篇文章描述了实现一个想法的努力:以JIT(动词)算法和由它们所使用的,一起的数据结构。

AVL树简介

下面是一个简短的介绍,以AVL树from Wikipedia:

在计算机科学中,AVL树是一个平衡树,它是被发明第一个这样的数据结构。在AVL树,任何节点的两个子子树的高度最多由一个不同;因此,它也被认为是高度平衡。查找,插入和删除都以O(日志ñ)时间的平均和最坏情况下,两个,其中n是节点在树中的号码之前,该操作。插入和缺失可能需要树由一个或多个树旋转来重新平衡。

问题和想法

当我们有一个数据结构和算法来处理(插入,删除和查找),其结构,我们的算法的本地代码通常是满的开销;例如,在一个AVL树(平衡二叉树),开销出现在:检查,如果我们真的有左或右节点,而对于遍历查找节点,接入节点内的节点,等这方面的负担造成不必要的组装业务,其在转,创建本机代码的开销,甚至当编译器优化。此开销对我们的算法的性能直接影响(这种传统的做法,当然,给我们一个非常灵活的结​​构和复杂性(亚洲金博宝没有大O) is easy to handle, but we pay for it: performance loss).

阅读更多