IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> 【Hard Python】【第三章-GC】1、引用计数与内存释放机制 -> 正文阅读

[Python知识库]【Hard Python】【第三章-GC】1、引用计数与内存释放机制

对于编程语言runtime来说,建立起良好运转GC机制是非常必要的,像JavaGo,其GC机制都经历了复杂的演化,当然同时也为编程语言带来了更好的性能,这也是为什么这两门语言能成为主流服务端语言的原因之一。

相对于JavaGopython的GC机制是相对简约的,其中最基础的机制之一就是引用计数。当对象生成时引用计数为1;对象被其它对象引用时引用计数增加1;对象没有被引用,又退出作用域的话,引用计数归0;引用计数归0后,对象被销毁。

我们可以通过一个例子对引用计数机制进行研究:

def test_ref():
    a = '123456789123456789'
    del a

其反编译的结果是:

  8           0 LOAD_CONST               1 ('123456789123456789')
              2 STORE_FAST               0 (a)

  9           4 DELETE_FAST              0 (a)
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

STORE_FASTDELETE_FAST操作中,都用到了SET_LOCAL宏:

#define SETLOCAL(i, value)      do { PyObject *tmp = GETLOCAL(i); \
                                     GETLOCAL(i) = value; \
                                     Py_XDECREF(tmp); } while (0)

可以看到SET_LOCAL的操作是:将对应LOCAL位置的旧值拷贝到tmp指针,让后将新值赋给对应LOCAL位置,最后减少旧值tmp指针的引用计数

对于DELETE_FAST操作,SETLOCALvalue参数是NULL,这样对应LOCAL位置指针被赋值为NULL,旧值减少引用计数,这样就触发后续一系列操作了。

当调用Py_XDECREF时,实际执行了如下的操作:

static inline void _Py_XDECREF(PyObject *op)
{
    if (op != NULL) {
        Py_DECREF(op);
    }
}

#define Py_XDECREF(op) _Py_XDECREF(_PyObject_CAST(op))

define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
    
static inline void _Py_DECREF(const char *filename, int lineno, PyObject *op)
{
#ifdef Py_REF_DEBUG
    _Py_RefTotal--;
#endif
    if (--op->ob_refcnt != 0) {
#ifdef Py_REF_DEBUG
        if (op->ob_refcnt < 0) {
            _Py_NegativeRefcount(filename, lineno, op);
        }
#endif
    }
    else {
        _Py_Dealloc(op);
    }
#endif
}

void
_Py_Dealloc(PyObject *op)
{
    destructor dealloc = Py_TYPE(op)->tp_dealloc;
#ifdef Py_TRACE_REFS
    _Py_ForgetReference(op);
#endif
    (*dealloc)(op);
}

可以看到,如果对象内部的ob_refcnt引用计数归0,就会触发_Py_Dealloc逻辑,清空给对象分配的内存。

针对test_ref的例子,会调用py3的字符串析构逻辑,也就是unicodedealloc逻辑:

static void
unicode_dealloc(PyObject *unicode)
{
    switch (PyUnicode_CHECK_INTERNED(unicode)) {
    case SSTATE_NOT_INTERNED:
        break;

    case SSTATE_INTERNED_MORTAL:
    {
        struct _Py_unicode_state *state = get_unicode_state();
        assert(Py_REFCNT(unicode) == 0);
        Py_SET_REFCNT(unicode, 3);
        if (PyDict_DelItem(state->interned, unicode) != 0) {
            _PyErr_WriteUnraisableMsg("deletion of interned string failed",
                                      NULL);
        }
        assert(Py_REFCNT(unicode) == 1);
        Py_SET_REFCNT(unicode, 0);
        break;
    }

    case SSTATE_INTERNED_IMMORTAL:
        _PyObject_ASSERT_FAILED_MSG(unicode, "Immortal interned string died");
        break;

    default:
        Py_UNREACHABLE();
    }

    if (_PyUnicode_HAS_WSTR_MEMORY(unicode)) {
        PyObject_Free(_PyUnicode_WSTR(unicode));
    }
    if (_PyUnicode_HAS_UTF8_MEMORY(unicode)) {
        PyObject_Free(_PyUnicode_UTF8(unicode));
    }
    if (!PyUnicode_IS_COMPACT(unicode) && _PyUnicode_DATA_ANY(unicode)) {
        PyObject_Free(_PyUnicode_DATA_ANY(unicode));
    }

    Py_TYPE(unicode)->tp_free(unicode);
}

unicode的析构逻辑中,首先判断字符串是不是intern的(短字符串缓存),如果是的话会启用另外的析构逻辑,否则会跑到下面。最终调用的是Py_TYPE(unicode)->tp_free(unicode)逻辑。

void
PyObject_Free(void *ptr)
{
    _PyObject.free(_PyObject.ctx, ptr);
}

// _PyObject.free(_PyObject.ctx, ptr)
// python_d.exe
static void
_PyMem_DebugFree(void *ctx, void *ptr)
{
    _PyMem_DebugCheckGIL(__func__);
    _PyMem_DebugRawFree(ctx, ptr);
}

static void
_PyMem_DebugRawFree(void *ctx, void *p)
{
    /* PyMem_Free(NULL) has no effect */
    if (p == NULL) {
        return;
    }

    debug_alloc_api_t *api = (debug_alloc_api_t *)ctx;
    uint8_t *q = (uint8_t *)p - 2*SST;  /* address returned from malloc */
    size_t nbytes;

    _PyMem_DebugCheckAddress(__func__, api->api_id, p);
    nbytes = read_size_t(q);
    nbytes += PYMEM_DEBUG_EXTRA_BYTES;
    memset(q, PYMEM_DEADBYTE, nbytes);
    api->alloc.free(api->alloc.ctx, q);
}

// api->alloc.free(api->alloc.ctx, q)
static void
_PyObject_Free(void *ctx, void *p)
{
    /* PyObject_Free(NULL) has no effect */
    if (p == NULL) {
        return;
    }

    if (UNLIKELY(!pymalloc_free(ctx, p))) {
        /* pymalloc didn't allocate this address */
        PyMem_RawFree(p);
        raw_allocated_blocks--;
    }
}

一路走下来,最终会调用_PyObject_Free去彻底释放这块内存,_PyObject_Free会尝试通过pymalloc_freePyMem_RawFree两种方式对对象所占内存进行释放。其中前者是采用python自带的内存管理机制,后者是采用操作系统的free方法。

这里我们需要稍微了解一下python内存管理的机制。python内部维护了不同组相同大小块的内存池,其中有几个概念:

  • arena:管理一组pool
    • 维护可用pool数量及pool总量
    • 可以有多个arena
  • pool:管理一组相同大小的block的链表
    • 正在使用中(used,非空,非满)的pool集合,会单独由usedpools数组管理
    • 申请内存时,会优先从usedpools寻找可用pool。如果block数量不够,会新增block
    • 如果没有指定大小的block,会从arena新起一个pool然后分配对应block
    • 释放对象内存时,被释放的block被转移到单独的可用block链表
  • block:一个固定大小的内存块
    • python中,有不同的固定大小,以8/16字节对齐(ALIGNMENT宏)
    • 针对不同大小的对象,分配不同大小的block

了解了这些概念,再看python对象的内存回收逻辑,就很明白了:

static inline int
pymalloc_free(void *ctx, void *p)
{
    assert(p != NULL);

    poolp pool = POOL_ADDR(p);
    if (UNLIKELY(!address_in_range(p, pool))) {
        return 0;
    }

    assert(pool->ref.count > 0);            /* else it was empty */
    block *lastfree = pool->freeblock;
    *(block **)p = lastfree;
    pool->freeblock = (block *)p;
    pool->ref.count--;

    if (UNLIKELY(lastfree == NULL)) {
        insert_to_usedpool(pool);
        return 1;
    }

    if (LIKELY(pool->ref.count != 0)) {
        /* pool isn't empty:  leave it in usedpools */
        return 1;
    }

    insert_to_freepool(pool);
    return 1;
}

最终我们可以看到,pymalloc_free主要做了以下几件事情,完成对象的内存释放:

  • 通过POOL_ADDR宏,找到指针p对应pool位置
  • 将指针p对应的内存块放到freeblock链表头部
  • 减少pool的引用数。如果归零,将pool放到对应arenafreepools
  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-03-13 21:44:46  更:2022-03-13 21:45:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 21:52:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码