云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

谷歌云_联合国贸易数据库_限时特惠

小七 141 0

LuaJIT Hacking:从NYI列表中取出next()

在Cloudflare,我们是LuaJIT的大量用户,在过去,我们赞助了许多对其性能的改进。LuaJIT是一个功能强大的软件,可能是业界性能最高的JIT。但是要充分利用它并不是一件容易的事,有时对代码的某个部分做一个小小的更改,就会对其他已经优化的部分产生负面影响。在编写使用LuaJIT快速运行的Lua代码时,人们收到的第一条建议是"避免NYIs":因为是NYI(尚未实现)而无法编译的语言或库特性。这意味着他们在翻译中运行。Dwayne Bent的CC BY-SA 2.0图像LuaJIT的另一个非常吸引人的特性是FFI库,它允许Lua代码直接与C代码和内存结构交互。JIT编译器根据生成的机器语言组织这些内存操作,使之比使用传统的luacapi更高效。不幸的是,如果出于任何原因,使用FFI库的Lua代码必须在解释器下运行,那么性能会受到很大的影响。碰巧,在解释器下,FFI通常比luacapi或基本操作慢得多。对许多人来说,这意味着要么避免外国金融机构,要么承诺保持永久的警惕,以保持代码不落入解释器。优化LuaJIT代码在优化任何代码之前,重要的是确定哪些部分实际上是重要的。如果发送操作所花费的时间是加法运算的100万倍,那么在发送数据之前讨论什么是最快的数字加法是没有用的。同样,避免在代码类的初始化例程中使用NYI特性也没有好处,因为JIT甚至不太可能对它们进行优化,所以它们总是在解释器中运行。顺便说一下,它也非常快,甚至比LuaJIT的第一个版本还要快。但是优化Lua程序的核心部分,就像任何深层次的内部循环一样,可以大大提高整体性能。在类似的情况下,使用其他语言的经验丰富的开发人员会检查编译器生成的汇编语言,以查看源代码是否有一些更改可以使结果更好。命令行LuaJIT可执行文件在使用-jbc选项运行时提供一个字节码列表,一个统计分析器,用-jp选项激活,用-jdump激活跟踪列表,最后用-jdump提供所有JIT操作的详细转储。最后两个提供了很多信息,对于理解Lua代码在执行时的实际情况非常有用,但是要读取-jdump生成的大量列表可能需要大量的工作。另外,如果不完全了解LuaJIT中的跟踪编译器的实际工作原理,有些消息很难理解。一个非常好的特性是所有这些JIT选项都在Lua中实现。为了实现这一点,JIT提供了"钩子",可以在重要时刻使用相关信息执行Lua函数。有时,理解some-jdump输出实际含义的最佳方法是读取生成输出特定部分的代码。CC BY 2.0图片由Kevan提供引进织机在那里进行了几轮之后,由于对顺序生成的转储的限制感到失望,我决定编写一个不同版本的-jdump,它收集了更多的信息进行处理,并添加了交叉引用,以帮助在显示之前查看事物之间的关系。结果是loom,它显示了与-jdump大致相同的信息,但使用了更多解析的引用,并用HTML格式,包括表、列、链接和颜色。它帮助我理解了我自己的代码和LuaJIT本身的工作原理。例如,让我们考虑一个名为两个回路。lua:i=11000度对于j=11000 do结束结束使用-jv选项:$luajit-合资企业两个回路。lua[轨迹1两个回路。lua:2个回路][记录道2(1/3)两个回路。lua:1->1]这告诉我们有两个跟踪,第一个包含一个循环,第二个从另一个(1/3)的出口3生成,它的端点返回到跟踪1的开始。好的,让我们用-jdump获取更多细节:$luajit-jdump美元两个回路。lua----记录道1开始两个回路。lua:2个0009 FORL 4=>0009----迹线1 IR0001内景SLOAD#5 CI0002+int添加0001+10003>国际乐0002+10000004------回路------------0005+int添加0002+10006>内景:0005+10000007国际PHI 0002 0005----记录道1 mcode 470bcbffd1 mov dword[0x40db1410],0x10bcbffdc cvttsd2si ebp,[rdx+0x20]0bcbffe1添加ebp,+0x010bcbffe4 cmp ebp,0x3e80bcbffea jg 0x0bcb0014->1->循环:0bcbfff0添加ebp,+0x010bcbfff3 cmp ebp,0x3e80bcbfff9 jle 0x0bcbff0->循环0bcbffb jmp 0x0bcb001c->3----轨迹1停止->循环----记录道2开始1/3两个回路。lua:1个0时0010=>00050005 K短4 10006 K短5 10000007 K短6 10008 JFORI 4=>0010----第2道红外光谱0001编号SLOAD#1i0002数字加0001+10003>数字LE 0002+1000----记录道2 mcode 810bcbff79 mov dword[0x40db1410],0x20bcbff84 movsd xmm6,[0x41704068]0bcbff8d movsd xmm5,[0x41704078]0bcbff96 movsd xmm7,[rdx]0bcbff9a地址xmm7,xmm60bcbff9e ucomisd xmm5,xmm70bcbffa2 jb 0x0bcb0014->10bcbffa8 movsd[rdx+0x38],xmm60bcbffad movsd[rdx+0x30],xmm60bcbffb2 movsd[rdx+0x28],xmm50bcbffb7 movsd[rdx+0x20],xmm60bcbffbc movsd[rdx+0x18],xmm70bcbffc1 movsd[rdx],xmm70bcbffc5 jmp 0x0bcbffd1----轨迹2停止->1这告诉我们。。。嗯,很多事情。如果你仔细观察,你会看到相同的两个记录道,一个是一个循环,第二个从1/3开始,返回到轨迹1。每一个都显示一些字节码指令、IR列表和最后的mcode。有几个选项可以打开和关闭每个列表,还有更多信息,如分配给某些IR指令的寄存器、"快照"结构(允许解释器在编译跟踪退出时继续运行)等。现在使用织机:![](https://blog.cloudflare这是源代码,有相应的字节码,还有相同的两个跟踪,有IR和mcode列表。记录道和顶部列表上的字节码行是链接的,在IR列表上的一些参数上悬停,突出显示每个值的来源和用途,记录道之间的跳转被正确标记(并着色),最后,单击字节码或IR列标题会显示更多信息:分别是源代码和快照格式的摘录。写它是一个很好的学习经验,我必须阅读dump脚本的Lua源代码,并且比以前更深入地研究LuaJIT源代码。然后,我不仅可以使用loom分析和优化Cloudflare的Lua代码,还可以观察编译器为了使其快速运行而经历的步骤,以及在它不高兴时发生的事情。代码就是代码代码代码就是代码LuaJIT最多处理四种不同的程序代码表示:首先是源代码,即开发人员编写的代码。解析器分析源代码并生成字节码,这是解释器实际执行的内容。它有相同的源代码流,按函数分组,有所有的调用、迭代器、操作等等。当然,没有很好的格式设置、注释,局部变量名被索引代替,所有常量(除了小数字)都存储在一个单独的区域中。当解释器发现字节码的某个给定点被重复多次时,它被认为是代码的"热点"部分,并再次解释它,但这次它记录了它遇到的每个字节码,生成"代码跟踪"或"跟踪"。同时,它会在代码执行时生成代码的"中间表示"(intermediate representation,简称IR)。IR并不代表整个函数或代码部分,只是它实际使用的实际选项。当跟踪遇到循环或递归、返回到比启动时更低的级别、遇到NYI操作或只是变得太长时,它就结束了。在这一点上,它可以被编译成机器语言,或者在它到达一些不能正确翻译的代码时被中止。如果成功,字节码将通过机器码或"mcode"的条目进行修补。如果中止,初始跟踪点将被"惩罚"甚至"块列表",以避免浪费时间尝试再次编译它。下一步是什么()?Lua语言最明显的特点之一是大量使用称为表的dictionary对象。从Lua手册:表是Lua中唯一的数据结构机制,可以用来表示普通数组、符号表、集合、记录、图形、树等要迭代表中的所有元素,惯用的方法是使用标准库函数对(),如下所示:对于k,v成对(t)do--使用"k"中的键和"v"中的值结束在标准的Lua手册中,pairs()被定义为"返回三个值:下一个函数、表t和nil",因此前面的代码与:下一个是k,v,t,nil do--使用"k"中的键和"v"中的值结束但是不幸的是,next()和pairs()函数在令人恐惧的NYI列表中都被列为"未编译"。这意味着任何这样的代码都在解释器上运行,并且不会被编译,除非里面的代码足够复杂,并且有其他的内部循环(当然,不使用next()或pairs()的循环)。即使在这种情况下,代码也必须在每个循环结束时返回到解释器。这个不幸的消息带来了一个折衷:对于代码中对性能敏感的部分,不要使用最类似Lua的代码样式。这激发了人们想出一些方法来使用数值迭代(这是经过编译的,非常高效),比如用数字替换任何键,将所有键存储在一个编号数组中,或者将键和值都存储在偶数/奇数的数字索引中。把下一个()从纽约人名单中除名所以,我终于有了一个非NYI next()f