python jit_Pypy Python的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代码编译为常见的可执行包,你要调用"rpython"转换程序。下面是一个从PyPy源代码仓库中提取简单的“你好,世界”的例子,它可以直接运行:

$> python ./rpython/bin/rpython ./rpython/translator/goal/targetnopstandalone.py

[...lots and lots of compiler output...]

$>

$> ./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 compiler output...]

$>

$> node ./targetnopstandalone-js

debug: hello world

$>

这就是所有要做的。如果你还有多余时间的话,那么你可以执行下面命令把整个PyPy解释器转换为JavaScript:

> python ./rpython/bin/rpython --backend=js --opt=2 ./pypy/goal/targetpypystandalone.py

[...seriously, this will take forever...]

^C

$>

或者你只想获取最终的结果: pypy.js

未压缩的情况下生成的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。我将一个接着一个地添加这些内置模块。

我肯定不会像repl.it那样在它的上面放一个基于浏览器的华丽的用户界面(UI)。

因此即便没有这些,你也不可能立刻在浏览器里运行这个。不过它是真正的Python解释器,而且它还可以执行真正的Python命令。对我来说,以些许连接代码的代价获得所有这些就非常了不起。

性能

当然大的问题时它是怎样执行呢?为了分析这个,我求援于Python社团的最流行的并且不科学的基准:pystone。这是一个没有意义的小程序,它用来

测试Python解释器执行循环的次数,并以“每秒执行pystone的个数“这样的结果来显示速度。下面是我在我的机器上对各种Python解释器测试

的结果;数值越大性能越好:

解释器

Pystones/秒

pypy.js,

href="https://developer.mozilla.org/en-US/docs/SpiderMonkey"

rel="nofollow">SpiderMonkey JavaScript shell的每晚构建下运行的编译了的pypy.js。这是强化Firefox的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="http://asmjs.org/"

rel="nofollow">asm.js规范里明确地呼吁在代码运行的任何阶段都可以生成和连接新的asm.js模块。由于

JavaScript具有动态特性,所以即时编译完全得到了支持,并且完全按照规范所期望那样运行。

然而,以asm.js方式运行的代码禁止为自身创建新的函数。如果pypy.js解释器需要即时编译某些代码,那么它将不得不通过调用外部的

JavaScript函数来跳出asm.js的快速运行通道。实际上运行生成的代码同样需要外部跳板以允许解释器跳出自身的asm.js模块去调用新的代

码,即时编译的代码也需要类似的跳板以回调主解释器。

这种asm.js内部模块连接是Emscripten路线图的一个试探性的内容,而且不清楚它将需要多少资源开销。如果前后跳跃所需要的所有开销太高,那么它就容易地受困于即时编译代码可能带来的性能好处里。

在PyPy方面还有一些潜在的障碍。PyPy开发者多次试图在低级虚拟机(LLVM)上构建自己的即时编译系统,然而重复多次后发现他们的需求受到了太多限制。提出主要的原因之一是没有能力动态地为生成的机器码打补丁,不能通过JavaScript即时编译(JIT)后台实现共享。

对我来说,如何限制仍然不清楚。如果牺牲一些效率,比如向生成的代码里加入其它检查和标志变量,就能够找到限制运行的地方,那么我们也许就可以从即时编译器知道问题所在。然而如果对代码动态地打补丁是即时编译操作的基础,那么我们也许纯粹是运气不好了。

最后,有人只需要试试,然后看看结果。假若我能找到这样的时间的话,我也计划这么做。

常常有这样的报道:带有即时编译的PyPy在某些基准测试上比CPython要快6倍或者更多。而且我们已经看到了asm.js代码比本地代码运行要慢不到三倍。结合这两个数据,今年剩余的时间我的崇高的、疯狂的、良好冬季但可能徒劳的目标如下:

让运行在spidermonkey shell里的pypy.js获得比本地CPython解释器更快的以每秒pystone数计量的速度。

可能吗? 我不知道。不过找到了将是很有趣的一件事!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值