|
|
51CTO旗下网站
|
|
移动端
创建专栏

我到Python虚拟机里逛了一圈,回来就被干掉了!

我出生在C盘一个很深的目录下,也不知道是谁把我放到这里的。

作者:刘欣|2019-12-02 10:34

我出生在C盘一个很深的目录下,也不知道是谁把我放到这里的。

我无事可干,整天就是睡觉,睡醒了就和我的邻居Account.class聊天,他曾经去过一次内存的Java虚拟机,不停地给我重复他的JVM奇遇记,什么陌生警察,什么虚拟机大楼,什么清理者,让我听得心痒痒的,也想来一次这样的冒险。

他告诉我:冒险经历的开端是两个警察,你就等着他们来吧。

1

陌生警察

这一天我正在睡觉,突然咣咣有人砸我房门。

我打开门一看,一高一矮两个陌生警察!我的冒险之旅要开场了。

“你们是ClassLoader吧?” 我想起了Account.class告诉我,会有个叫ClassLoader的警察来装载。

“什么ClassLoader? 我们Python不玩Java那一套!” 凶神恶煞的矮个子警察递上了工作证:“我是Python编译器,现在奉命对你的住处进行检查,有没有私藏pyc文件?”

“pyc? 什么pyc?” 我感觉情节发展和Account.class说得明显不符。

“别装了你!” 他四处查看,没一会儿,在一个叫做_pycache_的角落里拉出来一个叫做user.pyc的家伙,“敢说你没有私藏文件?”

我真是惊呆了,我确实是user.py,这个pyc是什么时候藏在这里的。

“让我检查检查,” Python编译器拿着放大镜开始查看pyc这个家伙的二进制数据,“嗯,Magic Number是3394,是我们Python3.7编译出来的,不过从修改时间戳看,实在是太老了。”

Python编译器刚说完,抽出手枪,砰的一声,就把这个pyc该干掉了, 他把头转向我:“现在,我对你重新编译。”

可怜的pyc,连个台词都来不及说,就消失在空气中了。

“有个叫order.py 的文件 import了你,现在我们奉命带你去内存编译。” Python编译器冷冰冰地说到。

我很惊奇:“我们Python不是解释执行吗,怎么还要编译?”

“真是无知,我们Python有虚拟机,执行的是字节码,是先编译,再解释执行!走,去内存编译。”

两个警察不允许我带任何东西,便把我推上车,我们一起奔向内存。

2

打探消息

我觉得前途未卜,不会编译完以后把我也干掉吧?不能坐以待毙,一定得多了解信息。

“警察大哥,你们是怎么找到我的?” 我小心地问那个高个警察。

高个儿警察还算和蔼,挥了挥手中的一个本子:“我是Python解释器,我们会根据本子上记录的Python模块搜索规则来查找,你看,先从程序运行的当前目录找,然后从PYTHONPATH找,然后是python的安装设置相关的默认路径。”

“瞧瞧,” 他指着本子说,“你就在C:\users\andy\temp\python\这个目录下。”

我心说这和Java的ClassPath差不多。

“原来如此,那为什么把那个pyc给枪毙了?” 我心里紧张,下意识地看了一眼开车的Python编译器。

“编译一次挺花费时间的,所以就把字节码缓存到了pyc文件中,如果你的源码没有变化,下次就不用编译,直接执行了。否则,那个pyc文件就没用了。”

我长出一口气,看来我的源码有改动!

“咱们怎么不用ClassLoader呢,我听说Java都是这么干的。”

“说来话长,” 高个儿警察很有耐心,“他们Java最早的时候有个非常先进的理念,代码可以从网络下载,在本地的JVM的执行, 但是你怎么知道网上的那些代码有没有危害?所以就搞了一个沙箱机制,ClassLoader也分了层,Java的核心类(如java.lang.String)只能由最上层的ClassLoader来装载,防止别有用心的人写个同名的核心类搞破坏。”

我点头:“奥,我们Python没有这样的需求,拿到源文件,编译后解释执行,也就不需要复杂的Class Loader了。”

3

编译

说话间,车子就开到了内存。

Python编译器下车,把我的代码通通搬到内存,然后是一系列让人眼花缭乱的词法分析,语言分析, 形成抽象语法树,从抽象语法树中形成字节码,此处略去3000字不表。

终于,他在内存中把我变成了二进制的字节码。

“这是什么鬼? ”

Python编译器说:“这就是pyc啊,就是PyCodeObject,编译一次累死人,我把这个PyCodeObject的对象保存到pyc文件中,下一次就不用编译了。”

“我给你举个例子,”高个的Python解释器接口道,“在你的user.py中有这么一段代码

def add(a,b):

c = a + b

print(c)

编译成PyCodeObject以后大概是这个样子:

(注:这里展示的只是一个片段,实际的PyCodeObject经常是一个复杂的嵌套接结构)

局部常量表中记录的是局部变量a,b,c 。

符号表中记录了程序引用的符号,如print等。

字节码就是真正的指令了,这些指令会引用常量表和符号表。”

只是展示一个片段就这么复杂了,我懒得去看这么多的细节,心里想着按照Account.class的剧本,接下来就要去方法区了。

可是高个子的Python解释器说:“我们这儿没有方法区,Python的对象和数据结构都是保存在一个Heap中的,user.py,这是你的地址,你带着PyCodeObject到那里去吧,一会儿就有线程联系你了。”

4

执行

去Heap区的路上,我看到一队全副武装的士兵不停地在巡逻,时不时把一些对象拉出来,塞到车里,不用说,这些都是可怕的清理者。

我仔细观察了一下,每个对象的头上都有一个引用计数,如果被使用,计数就会增加,不用就会减少,如果变成零,对不起,那就危险了。

按照地址找到了格子间,我俩刚坐下来,桌子上的视频电话就响了。

画面中,我看到一个编号为0x7954的线程坐在一个明亮的CPU车间里,他的面前是一个工作台,工作台上有一个深桶(后来知道这叫做栈)和一排小格子,还有一个引人注目的大锁,上面写着“GIL”。

这个线程对我说:“我是线程0x7954,我们的老板Python解释器让我调用你的add函数,请把第一条指令给我说一下。”

我说:“c = a +b ”

“听不懂,你得给我说字节码。”

我恍然大悟,赶紧从PyCodeObject中的字节码区域寻找:“LOAD_FAST 0 (a)”

0x7594从编号为0的格子中找到了数字10, 也就是add函数的参数a 的值,放入栈中

然后0x7594说:“下一条指令。”

“LOAD_FAST 1 (b)”

于是数字20被放入了栈中:

然后是:BINARY_ADD, 这应该是个加法操作。

0x7954迅速地把10,20都取出来,做了加法,把结果30放入栈中。

最后是 :STORE_FAST 2 (c)

于是0x7954取出30,放到了编号为2的格子中

看到这里, 我就明白了Account.class曾经说过JVM是个基于栈的虚拟机, 看来Python VM也是如此啊。

不过既然都是虚拟机,为什么这里执行两个整数的加法操作(BINARY_ADD)会这么慢呢?

电话那头的0x7954似乎看透了我的心思:“我最烦这个BINARY_ADD指令了,Python是动态类型语言,运行期才知道具体类型,比如这段代码

s1 = "hello"

s2 = "world"

s = s1 + s2

编译后,底层的指令也是BINARY_ADD, 所以在执行这个指令的时候,还需要做类型判断,如果操作数是整数,就相加;如果操作数是字符串,就做连接;如果一个是整数,一个是字符串,还得做转型,我容易吗我!”

看来静态类型也有好处,可以直接编译成对应的字节码,整数相加就是iadd,字符串连接是其他字节码,在运行时就不用判断参数类型了。

5

GIL

执行的时间长了,我对这些字节码熟得都能背下来了,这里实在是无聊。

0x7954执行完一条STORE_FAST指令以后,居然停了下来,我心中大喜,Account.class告诉过我,一旦停下来,那就是程序员要调试了,他们的一秒是我们的十多天,将会有个漫长的假期。

但是没有什么调试, 0x7954从工作台上抱起GIL这个大锁离开了CPU车间。

他对我说:“对不起,刚才Python解释器说我已经运行了100个ticks,必须得放弃这个GIL的锁,让别的线程使用CPU车间了。”

我说:“不对啊,你这里有4个CPU车间(CPU core),你为什么不去别的车间执行?”

“没办法,这是老大规定的,不管有多少个CPU车间,只有抢到GIL锁的哪个线程才能运行。”

“这么多线程在等待GIL,这么多CPU车间空着,一核有难,多核围观,浪费啊,浪费!” 我不由得痛心疾首。

不知道等了多久,0x7954又获得了GIL锁,进入CPU车间执行。

我注意到一个特点,字节码中对print函数的调用特别特别多。

程序员们怎么不调试呢?快乐假期怎么还不来呢?

0x7954说:“码农有两类

1. 调试派,出了问题喜欢调试

2. 输出派,不喜欢单步调试,喜欢通过print来输出信息

3. 思考派,出了问题先在脑子中分析定位,然后再调试。

我看咱们这位Python程序员属于第二种。”

这个程序员“去年”还调试Java呢,怎么到了Python这里就变成输出派了?我很疑惑。

6

尾声

代码终于执行完了,整个世界都消失了,我又回到了硬盘,正如Account.class所说,像做了一场梦一样。

user.pyc热情地给我打招呼:“大哥回来了,你可千万别再改动了,你一改动我就完蛋。”

我说:“我也不想改,一改我也活不成, 但是我也控制不了程序员啊......”

话还没说完,就感觉头上遭遇了一记暴击,我知道程序员动了我的源码,也许是修改了一个Bug,我知道自己要被新版本覆盖了。

user.pyc喃喃自语:“完了,这么快就改了.....”

这时候门外又响起了敲门声......

【本文为51CTO专栏作者“刘欣”的原创稿件,转载请通过作者微信公众号coderising获取授权】

戳这里,看该作者更多好文

【编辑推荐】

  1. 特别推荐!优化​Python开发环境的几个技巧,实现自动化让你远离烦恼
  2. 一份不可多得的数据科学与机器学习Python库
  3. 7本经典的Python书籍,你都读过了么?
  4. 你这个Python异教徒!
  5. 代码详解:如何用Python快速制作美观、炫酷且有深度的图表
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢