首页 存档 技术 查看内容

禁用Python的GC机制后,Instagram性能提升了10%

2018-3-30 13:00 |来自: 互联网 300 0

摘要: 作者| Chenyang Wu 编辑| 刘志勇 通过关闭Python垃圾回收(Garbage Collection,GC)机制(通过回收和释放未使用的数据来回收内存),Instagram的性能提高了10%。是的,你没有听错!通过禁用GC,我们可以减少内 ...


作者| Chenyang Wu
编辑| 刘志勇

通过关闭Python垃圾回收(Garbage Collection,GC)机制(通过回收和释放未使用的数据来回收内存),Instagram的性能提高了10%。是的,你没有听错!通过禁用GC,我们可以减少内存占用并提高CPU LLC缓存命中率。如果你想知道为什么,那么就来阅读Chenyang Wu和Min Ni为此撰写的文章(点击阅读原文查看)。

作者Chenyang Wu是Instagram的软件工程师,Min Ni是Instagram的技术经理。

我们如何管理Web服务器

Instagram的web服务器以多进程的模式运行在Django上,主进程分叉创建几十个工作进程,用来接收传入的用户请求。对于应用程序服务器,我们使用带前置模式的uWSGI来利用主进程和工作进程之间的内存共享。

为了防止Django服务器运行到OOM,uWSGI主进程提供了一种机制,当其RSS内存超过阈值时重新启动工作进程。

了解内存

我们开始研究工作RSS内存为什么在由主进程产生后迅速增长。一个观察是,即使RSS存储器以250MB开始,其共享内存下降也会非常快:在几秒钟内从250MB降到约140MB(共享内存的大小可以从/proc/PID/smaps读取)。这里的数字并没有什么实际意义,因为它们一直在变动,但共享内存丢弃的规模却很有趣:大约1/3的总内存。接下来,我们想要了解为什么共享内存在工作器产生伊始就变为每个进程的私有内存。

我们的理论:读时复制

Linux内核有一个称为写入复制(Copy-on-Write,CoW)的机制,用作分叉进程(fork)的优化。写入时复制是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。(有关详细信息,请参阅维基百科上的Copy_on_Write词条)。

但在Python中,由于引用了计数,事情变得有趣了。每次我们读取一个Python对象时,解释器将增加其引用计数,这本质上是对其底层数据结构的写入。这就导致了CoW。因此,使用Python,我们就进行读时复制(Copy-on-Read,CoR)!

那么问题是:我们是在写时复制不可变对象(如代码对象)么?给定PyCodeObject确实是PyObject的“子类”,那么答案显然为:是。我们的第一个想法,是禁用对PyCodeObject的引用计数。

尝试1:禁用代码对象的引用计数

在Instagram,我们先做简单的事情。考虑到这是一个实验,我们对CPython解释器做了一些小的修改,验证了引用计数对代码对象没有改变,然后将CPython应用到我们的一个生产服务器。

结果令人失望,因为共享内存没有变化。当我们试图找出原因时,我们意识到没有任何可靠的指标来证明分析是否正确,也不能证明共享内存和代码对象的副本之间的关系。显然,这里缺少一些什么东西。由此获得的经验是:在运作之前证明你的理论。

分析页面故障

当我们在Google上搜索关于Copy-on-Write的资料后,了解到Copy-on-Write与系统中的页面错误是相关联的。每个CoW在过程中触发页面错误。Linux附带的Perf工具允许记录硬件/软件系统事件,包括页面错误,甚至可以提供堆栈跟踪!

于是我们运行了一个prod服务器,重启服务器后,等待它进行分叉,得到了一个工作进程的PID,然后运行以下命令:

perf record -e page-faults -g -p

声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部