PypyPython的JIT实现
Pypy从表⾯意思上⾯来说的话,就是⽤Python实现的Python。但是更准确的描述应该是RPython实现的Python。
RPython是Python的⼦集,为什么到现在CPython⼀直没有加⼊JIT功能,就是因为它的变量的类型是运⾏时确定的,也正是因为这样,JIT很难做。
x = random.choice([1, "foo"])
在编译期,很难确定这是x是什么类型的,所以JIT优化很难做,这个时候就使⽤了RPython来实现Python的语法了,但是它具有静态类型,这样JIT实现起来更加容易。
RPython实现的是Python代码的解析器,所以它只是⼀个编译器的前端,那么编译器的后端是什么呢?⽬前Pypy只实现了Python到C的编译,也就是说编译器的后端实现了直接转成了机器码。
当然,编译器后端也可以编译成Java字节码,C#字节码。这个只是暂时没有实现⽽已。
Pypy之所以难以理解,就是因为很多程序员并没有学习过编译原理,简单介绍下,
1.编译前期,就相当于词法分析器,把源代码分析成中间格式,这种格式是不能执⾏的,需要进⼊第2个步骤
2.编译后期⽣成的⽂件就相当于字节码,⽣成了Java字节码就能在JVM上⾯执⾏,⽣成了.Net字节码就能在CLR虚拟机上⾯运⾏
Pypy和上⾯做⼀下对⽐就清楚了。
1.RPython的功能就是实现词法的分析
2.Pypy会根据RPython⽣成的中间⽂件,进⾏后期编译,因为RPython具有的静态类型,实现JIT更简单,这也是为什么使⽤RPython的原因
以上的部分描述并不是特别准确,但是通过简单的描述,可以更加⽅便的理解Pypy的原理。下⾯列出来⼀写参考。
对PyPy的伟⼤的⼯作⼈员和Emscripten的开发⼈员来说,现实中混合这两种技术⼏乎同理论上听起来那样容易。PyPy的RPython⼯具链有⼀个可以让你很容易地插⼊定制的编译器或者甚⾄插⼊⼀个完整的新⼯具链的扩展的地⽅。我的github分⽀就包含把它和Emscripten挂接所必须的逻辑:
Emscripten努⼒做到像标准的Posix构建链那样运⾏,这样只要求你⽤"emcc"替换通常所⽤的"gcc"调⽤。我确实需要做⼀点调整使它更像Posix运⾏环境,因此你需要⽤到下⾯的分⽀,直到它们与上⾯的分⽀合并为⽌:
为了把RPython代码编译为常见的可执⾏包,你要调⽤"r"转换程序。下⾯是⼀个从PyPy源代码仓库中提取简单的“你好,世界”的例⼦,它可以直接运⾏:
$> python ./rpython//rpython ./rpython/translator/goal/targetnopstandalone.py
[...lots and lots of ]
$>
$> ./targetnopstandalone-c
debug: hello world
$>
然⽽为了把RPython代码编译成JavaScript,你只需要指定选项"--backend=js"。⽣成的JavaScript⽂件可以使⽤如nodejs这样的JavaScript shell的命令⾏来执⾏:
$> python ./rpython/bin/rpython --backend=js ./rpython/translator/goal/targetnopstandalone.py
[...lots and lots of ]
$>
$> node ./targetnopstandalone-js
debug: hello world
$>
这就是所有要做的。如果你还有多余时间的话,那么你可以执⾏下⾯命令把整个PyPy解释器转换为JavaScript:
> python ./rpython/bin/rpython --backend=js --opt=2 ./pypy/goal/targetpypystandalone.py
[...seriously, this will ]
^C
$>
或者你只想获取最终的结果:
未压缩的情况下⽣成的JavaScript⽂件为139M.它包括完整的Python语⾔解释器,⼏个⾮常重要的内置模块以及附加的Python标准库中所
有.py⽂件的列表。如果你⼿边有⼀个JavaScrip shell的话,你可以像下⾯命令⾏这样传递这些参数JavaScript shell来运⾏Python命令:
$> node pypy.js -c 'print "HELLO WORLD"'
debug: WARNING: Library path not found, using compiled-in sys.path.
debug: WARNING: 'sys.prefix' will not be set.
debug: WARNING: Make sure the pypy binary is kept inside its tree of files.
debug: WARNING: It is ok to create a symlink to it from somewhere else.
'import site' failed
HELLO WORLD
$>
正如你所料,第⼀个版本有⾮常多的警告:
没有即时编译器(JIT)。在上⾯,我通过传递"--opt=2"选项显式地禁⽌了省城即时编译器。⽣成即时编译器需要⼀些平台相关的代码的⽀持,实际上我仍然没有弄清楚它应该看起来像什么。
没有⽂件系统访问权限,这使得在启动的时候就打印出调试的告警信息。这还需要做些对Emscripten扩展可插拔虚拟⽂件系统的⼯作,在将来的某个时刻可启⽤本地⽂件的访问权限。
然⽽,为了提供Python标准库,它使⽤了绑定⽂件系统的快照。这使得启动⾮常⾮常地慢,因为在进⼊解释器的主循环之前需要把整个快照解包到内存。
没有交互式控制台。输出运⾏正常,不过输⼊却并不是这样的。我仍然不想深挖细节,不过让⼀些基本的东西运⾏应该不是太难的。
丢失了许多内置模块,因为这些内置模块需要其他C级别的依赖。⽐如,"hashlib"模块依赖OpenSSL。我将⼀个接着⼀个地添加这些内置模块。
python新手代码你好我肯定不会像repl.it那样在它的上⾯放⼀个基于浏览器的华丽的⽤户界⾯(UI)。
因此即便没有这些,你也不可能⽴刻在浏览器⾥运⾏这个。不过它是真正的Python解释器,⽽且它还可以执⾏真正的Python命令。对我来说,以些许连接代码的代价获得所有这些就⾮常了不起。
性能
当然⼤的问题时它是怎样执⾏呢?为了分析这个,我求援于Python社团的最流⾏的并且不科学的基准:pystone。这是⼀个没有意义的⼩程序,它⽤来测试Python解释器执⾏循环的次数,并以“每秒执⾏pystone的个数“这样的结果来显⽰速度。下⾯是我在我的机器上对各种Python解释器测试的结果;数值越⼤性能越好:
解释器 Pystones/秒 pypy.js, href="/en-US/docs/SpiderMonkey" rel="nofollow">SpiderMonkey JavaScript shell 的每晚构建下运⾏的编译了的pypy.js。这是强化的JavaScript引擎,⽽且它能够识别和优化Emscripten⽣成的asm.js语法。果真,这个外加的优化实质上提⾼了速度。下⼀个最慢的是禁⽌了即时编译功能(JIT)的PyPy的本地构建。把这个版本与pypy.js相⽐就能对在JavaScript ⾥运⾏和本地代码运⾏所花费的资源开销有所了解,我们可以看到快了⼤约7倍。这甚⾄与在其他asm.js编译的代码上所呈现的只是慢两倍的结果相差很远。不过再说⼀遍,我没有做过任何研究或者调整性能的⼯作。我怀疑可能有⼀些相对容易实现的东西可以帮助缩短这个差距。
我的系统中较快的是本地Python解释器CPython 2.7.4。有时可能忘记的重要的⼀点是:没有即时编译器(JIT) ,PyPy解释器通常⽐标准的CPython解释器慢⼀些。这是它为实现灵活性⽬前必须付出的代价。然⽽任何事情需要的不是停留- PyPy的开发者⼀直在寻甚⾄在缺少即时编译器(JIT的情况下加速PyPy解释器。
⽏庸置疑,这⼉的速度之王是启⽤即时编译功能的PyPy本地构建。
⽐较pypy.js和启⽤了即时编译(JIT)的本地PyPy是很容易的,结论是两者根本就没有可⽐性。现在它们的速度差异是在两个数量级上!不过这只是第⼀次尝试,⽽且没有PyPy那样的特别的速度JavaScript版本依然正常运⾏。如果我们能够成功地把PyPy的即时编译(JIT)功能转换为 JavaScript,那么我们就能够弥补回⼤量这样的性能差距。的确这是⼀个相当⼤的“假设”,不过是⼀个有趣的可选项。
要想提前看看什么可能发⽣,请考虑⼀下PyPy仓库⾥pystone的单独的RPython版本。如果我们把它从RPython变为为本地代码,那么它将给出机器能⼒的⼤概上限。然⽽,如果我们把她从RPython编译为JavaScript,那么它将给出启⽤即时编译的PyPy的JavaScript选项可能的⼤概上限:
解释器
Pystones/秒 native rpystone 38461538 rpystone.js, href="/" rel="nofollow">asm.js规
范⾥明确地呼吁在代码运⾏的任何阶段都可以⽣成和连接新的asm.js模块。由于 JavaScript具有动态特性,所以即时编译完全得到了⽀持,并且完全按照规范所期望那样运⾏。
然⽽,以asm.js⽅式运⾏的代码禁⽌为⾃⾝创建新的函数。如果pypy.js解释器需要即时编译某些代码,那么它将不得不通过调⽤外部的JavaScript函数来跳出asm.js的快速运⾏通道。实际上运⾏⽣成的代码同样需要外部跳板以允许解释器跳出⾃⾝的asm.js模块去调⽤新的代码,即时编译的代码也需要类似的跳板以回调主解释器。
这种是Emscripten路线图的⼀个试探性的内容,⽽且不清楚它将需要多少资源开销。如果前后跳跃所需要的所有开销太⾼,那么它就容易地受困于即时编译代码可能带来的性能好处⾥。
在PyPy⽅⾯还有⼀些潜在的障碍。PyPy开发者多次试图在低级虚拟机(LLVM)上构建⾃⼰的即时编译系统,然⽽。提出主要的原因之⼀是没有能⼒动态地为⽣成的机器码打补丁,不能通过JavaScript即时编译(JIT)后台实现共享。
对我来说,如何限制仍然不清楚。如果牺牲⼀些效率,⽐如向⽣成的代码⾥加⼊其它检查和标志变量,就能够到限制运⾏的地⽅,那么我们也许就可以从即时编译器知道问题所在。然⽽如果对代码动态地打补丁是即时编译操作的基础,那么我们也许纯粹是运⽓不好了。
最后,有⼈只需要试试,然后看看结果。假若我能到这样的时间的话,我也计划这么做。
常常有这样的报道:带有即时编译的PyPy在某些基准测试上⽐CPython要快6倍或者更多。⽽且我们已经看到了asm.js代码⽐本地代码运⾏要慢不到三倍。结合这两个数据,今年剩余的时间我的崇⾼的、疯狂的、良好冬季但可能徒劳的⽬标如下:
让运⾏在spidermonkey shell⾥的pypy.js获得⽐本地CPython解释器更快的以每秒pystone数计量的速度。
可能吗?我不知道。不过到了将是很有趣的⼀件事!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论