pypy的垃圾回收机制

什么是pypy

pypy与python是同一门语言,只不过python是解释器,而pypy是JIT编译器,什么是JIT编译器可能需要一篇博客来说清楚.总的来说就是,两者的比较是,Python在运行的时候会将Python解释,然后运行.而pypy则是在运行这个代码的时候先将这一行代码的时候进行编译,然后再运行.

那这两者有什么区别呢?我们的程序永远不可能是顺序执行的,也就是说我们的代码必定用重复使用的区域,通过JIT技术能让重复执行的代码区域省去解释的开销.

我们以一个中国人想通过一本英文菜谱做饭举例:

  • 静态编译(static compile):先把整本英文书翻译成中文再做菜.
  • 解释器(interpreter):做菜的时候直接读英文,在脑子里翻译成中文然后做菜
  • JIT(just-in-time compile):如果这个菜谱没有中文,就先只把这个菜谱翻译成中文,然后读中文,如果这个菜谱以前翻译过,则直接使用之前翻译的中文,否则把这个菜英文翻译成中文

cpython的垃圾回收机制

cpython(也就是最经典python)的垃圾回收机制是引用计数回收,什么是引用计数回收呢,就是说如果你有一个变量使用了一块区域,ok,这个区域别标记成为1,如果有两个变量,则计数值标记成2.

当我们的编译器发现某一个变量使用完毕的时候(例如执行完某一个函数,返回)然后那么这个变量的区域的计数值就会减去1,然后判断这个计数值是否等于0,如果等于0则销毁这块区域.也就是说,我们用完了某一个变量,不用管,cpython会自动把这个变量销毁.所以我们在使用python打开一个文件的时候,有时候我们忘记写了关闭文件也没事,也不会内存溢出.python打开一个socket的时候,忘记了关闭也没事,当应用计数值到0,python会自动帮我们销毁.所以我们写python的时候非常舒心,都不需要进行内存管理.这样做的好处的就是我们把这些复杂的细节屏蔽掉,对于非专业用户来说简化了不少,这是python的一大优势.

pypy的垃圾回收机制

cpython这么做为了简单,在性能上做出一点牺牲无可厚非,或者说让python有了现在的发展.不过pypy这个是想在cpython的性能上补足短板的编程语言自然就不能这么做了.所以pypy使用的不是引用计数机制,虽然它的确也有垃圾回收.第一个明显的不同就是pypy不是引用计数这意味着当最后一个引用某一个资源的变量离开作用域的时候,这个资源并不会立即销毁.例如你打开一个文件,然后你没有关闭这个文件,然后这个函数返回,离开了变量的作用域,这个文件有可能并没有保存,还存在于buffer中.还有一种情况 就是比如你的文件描述符(fd)没有正确的关闭,然后一直打开新的文件描述符,这个是你就会超过系统限制(limits)然后程序崩溃掉.同时pypy还有full gc的问题,就是如果你的垃圾积累了足够多,pypy的gc可能会the whole world stop 10~100ms的样子,然后做垃圾回收.所以如果对延时要求特别低的程序,还是写静态语言吧.

垃圾回收内存布局

pypy采用的是Incminimark的垃圾回收引擎,这是一个增量垃圾回收,增量垃圾回收的目的是使一次full gc变成多个步骤,然后一次一个步骤,通过这样的方式降低每一次程序停止的时间.pypy的内存区域分成两块:"新生区"(新生成的变量都是在这块区域中分配),以及"大区",然后将"大区"分成多片,以便分布进行垃圾回收,从而让每一步的停止时间都不算长.

垃圾回收步骤

  • 新生区:所有的新对象都是分配于"新生区",当然"新生区"满的时候,遍历"新生区"还存活的对象,移动到"大区"中(有时候也会扫描新生区里面的所有对象以找到谁是存活的,谁已经死掉了),这边步骤被称为"小回收"
  • 大区:在某些条件下,会在"小回收"后触发"大回收",这就是传统的标识删除方法了(mark-and-sweep step),他会扫描所有内存区域,并做标记(三色标记),然后对某些特定的对象做回收操作.但是一次性扫描整个区域真的是太慢了,而且会让程序停下来,所有这里采用了增量方法,也就是一次只扫描一个片.这样单次回收的时间就降低了.

参考