Biased Reference Counting

Multithreaded Python without the GIL

Python PEP 703文章中提到,Python有望告别Global Interpreter Lock (GIL)。文章作者Sam Gross来自Facebook,同时他也是NoGIL项目的作者,关于这个项目的说明可以看这个Google的文档或者Github。

这篇文章对GIL的讲解还是很不错的。基于CPython解释器的Python多线程在执行时,由于GIL锁的限制,任意时刻只会有一个python线程在执行,对于单线程程序几乎没有什么影响,但对于多线程来说会造成性能瓶颈。Nogil这个项目就是来解决python多线程下GIL的问题,在没有GIL,能够充分发挥并行化带来的好处。

本文主要记录下Nogil项目中引用计数的修改。CPython在没有GIL的情况下,是非线程安全的,即多线程的情况下,程序不会按照预期的行为去执行。最简单的方式是将非原子(non-atomic)的操作替换成原子操作,但是这会需要很大的成本,文章提到这种方式会导致性能下降约60%。在Nogil项目中使用了Choi 等人在2018年提出的biased reference counting (BRC)方案,原理很简单,因为大部分对象都是在单个线程中被修改,少数是在多线程中被修改,而且non-deferred RC中原子操作耗时,所以就要想办法减少原子操作。于是,BRC中将操作对象的线程分为两种,创建这个对象的线程为owning thread,其它使用该对象的线程为other threads,同时reference counting也分为localshared
owning thread使用非原子指令修改local计数,而其它线程的只能用原子指令修改shared引用计数,当local计数变为0时owning thread需要放弃对象的所有权。

上图中的figure 4显示了swift语言中,采用引用计数(RC)的对象头结构,32bit用于计数,剩下34bit保留用于弱引用和记录对象状态。Figure 5中BRC方案将64bit分为BiasedShared两部分,Biased部分用于owning thread,TID代表线程id,其它线程的操作将在Shared中记录,详细字段说明请参看论文。

关注字段大小会发现,原来的计数有30bits,BRC中的计数只用了14bits,论文中也说明14bits用于计数已经足够。

参考

  1. https://github.com/colesbury/nogil
  2. https://docs.google.com/document/d/18CXhDb1ygxg-YXNBJNzfzZsDFosB5e6BfnXLlejd9l0/edit#
  3. https://dl.acm.org/doi/pdf/10.1145/3243176.3243195