Python垃圾回收及Linux Fork

前言:

在口袋助理看到瞭其他部門的同事針對Python2內存占用做的一點優化工作,自己比較感興趣,遂記錄下。

1.Linux fork簡介

fork是Linux提供的創建子進程的系統調用。為瞭優化創建進程速度,Linux內核使用瞭Copy-on-Write的方式去創建進程,所謂Copy-on-Write是指執行fork之後,
內核並不立即給子進程分配物理內存空間,而是讓子進程的虛內存映射到父進程的物理內存。僅僅當子進程向地址空間中執行寫入操作時,才給它分配一段物理內存。
通過這種方式既優化瞭進程創建的時間,又減少瞭子進程的內存占用。

1.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;

3.解決辦法

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()

到此這篇關於Python垃圾回收及Linux Fork的文章就介紹到這瞭,更多相關Python垃圾回收及Linux Fork內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

參考:

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

推薦閱讀: