转自:https://www.cnblogs.com/hitfire/articles/4502199.html
Pypy从表面意思上面来说的话,就是用Python实现的Python。但是更准确的描述应该是RPython实现的Python。
RPython是Python的子集,为什么到现在CPython一直没有加入JIT功能,就是因为它的变量的类型是运行时确定的,也正是因为这样,JIT很难做。
1
|
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挂接所必须的 逻辑:
https://github.com/rfk/pypy
Emscripten努力做到像标准的Posix构建链那样运行,这样只要求你用"emcc"替换通常所用的"gcc"调用。我确实需要做一点调整使它更像Posix运行环境,因此你需要用到下面的分支,直到它们与上面的分支合并为止:
https://github.com/rpk/emscripten
为了把RPython代码编译为常见的可执行包,你要调用"rpython"转换程序。下面是一个从PyPy源代码仓库中提取简单的“你好,世界”的例子,它可以直接运行:
1
2
3
4
5
6
|
$> python .
/
rpython
/
[...lots
and
lots of compiler output...]
$>
$> .
/
targetnopstandalone
-
c
debug: hello world
$>
|
然而为了把RPython代码编译成JavaScript,你只需要指定选项"--backend=js"。生成的JavaScript文件可以使用如nodejs这样的JavaScript shell的命令行来执行:
1
2
3
4
5
6
|
$> 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:
1
2
3
4
|
> python .
/
rpython
/
bin
/
rpython
-
-
backend
=
js
-
-
opt
=
2
.
/
pypy
/
goal
/
targetpypystandalone.py
[...seriously, this will take
^C
$>
|
或者你只想获取最终的结果: pypy.js
未压缩的情况下生成的JavaScript文件为139M.它包括完整的Python语言解释器,几个非常重要的内置模块以及附加的Python标准库中 所有.py文件的列表。如果你手边有一个JavaScrip shell的话,你可以像下面命令行这样传递这些参数JavaScript shell来运行Python命令:
1
2
3
4
5
6
7
8
|
$> 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
$>
|
正如你所料,第一个版本有非常多的警告:
因此即便没有这些,你也不可能立刻在浏览器里运行这个。不过它是真正的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数计量的速度。