前言
在口袋助理看到了其他部門的同僚針對Python2記憶體占用做的一點優化工作,自己比較感興趣,遂記錄下。
Linux fork簡介
fork是Linux提供的建立子程序的系統調用。為了優化建立程序速度,Linux核心使用了Copy-on-Write的方式去建立程序,所謂Copy-on-Write是指執行fork之後,
核心并不立即給子程序配置設定實體記憶體空間,而是讓子程序的虛記憶體映射到父程序的實體記憶體。僅僅當子程序向位址空間中執行寫入操作時,才給它配置設定一段實體記憶體。
通過這種方式既優化了程序建立的時間,又減少了子程序的記憶體占用。
Copy-On-Write政策增加Python多程序記憶體占用的原因
Python GC采用引用技術的方式去管理對每個對象的引用,每一個被GC跟蹤的對象會由一個PyGC_Head的結構體去表示。
如下所示,其中gc_refs就是每個對象的引用計數值,當我們在子程序中讀取父程序建立的對象的時候,就會導緻子程序的虛位址空間中的gc_refs加1,
進而觸發了核心的缺頁中斷,這是核心就會給子程序建立新的實體記憶體。僅僅是簡單的讀取操作就會導緻新的記憶體空間産生。
/* GC information is stored BEFORE the object structure. */
typedef union _gc_head
{
struct {
union _gc_head *gc_next;
union _gc_head *gc_prev;
Py_ssize_t gc_refs;
} gc;
long double dummy; /* force worst-case alignment */
} PyGC_Head;
解決辦法
python3的解決方法
針對這個問題,Python3.7增加了三組API(有instagram團體送出的)[1]。

freeze用于将GC追蹤的所有對象都移動到永生代(permanent generation),之後垃圾回收會忽略這些被設定為永生代的對象。
實際使用中,我們可以在父程序中執行freeze函數,然後子程序中使用和父程序共享的對象,這樣對象的引用技術就不會增加,進而避免了COW的發生。
python2的解決方法
(1) 針對Python2,我們可以簡單的把Python3的相關函數移植過來
(2) 使用multiprocessing.Array去共享資料。Array會從共享記憶體中取一段取存儲資料,并不會增加引用技術值,進而觸發COW。
實作方面,Array使用Posix共享記憶體 + mmap去實作。[3]
#!/usr/bin/env python
# coding=utf-8
from multiprocessing import Array
import os
import sys
def foo():
shared_cache = Array('i', range(0, 100), lock=False)
pid = os.fork()
if pid > 0:
print("parent:", sys.getrefcount(shared_cache))
elif pid == 0:
print("child:", sys.getrefcount(shared_cache))
foo()
參考
1.https://instagram-engineering.com/copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf
2.https://llvllatrix.wordpress.com/2016/02/19/python-vs-copy-on-write/
3.https://github.com/python/cpython/blob/main/Lib/multiprocessing/shared_memory.py