古明地觉的编程教室

解密 bytes 对象的缓存池

bytes 对象是不可变对象,那么根据我们对浮点数的了解,可以大胆猜测 bytes 对象也有自己的缓存池。

// Include/internal/pycore_global_objects.h
struct _Py_static_objects {
    struct {
        // 该结构体保存了一些静态分配的对象,它们都是单例的
        // 小整数对象池
        PyLongObject small_ints[_PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS];
        // 空的 bytes 对象
        PyBytesObject bytes_empty;
        // bytes 对象缓存池
        struct {
            PyBytesObject ob;
            char eos;
        } bytes_characters[256];
        // ...
    } singletons;
};

结构体里的 bytes_characters 便是 bytes 对象的缓存池,这是一个长度为 256 的数组,这意味着缓存池里面可以容纳 256 个 bytes 对象。

但 _Py_static_objects 保存的是静态分配的单例对象,也就是说在解释器启动之后这些对象就已经被初始化好了,所以 bytes 对象缓存池和小整数对象池一样,里面存储的对象是固定的。

小整数对象池里面存储的是 -5 ~ 256 之间的整数,而 bytes 对象缓存池里面存储的是 256 个单字节 bytes 对象,即 b"\x00"、b"\x01"、····、b"\xff"。所以解释器对长度为 1 的 bytes 对象是做了优化的,它们只会存在一份,因为一个字节的最大值为 255,所以 bytes_characters 里面保存了 256 个 bytes 对象。

当然以上都只是结论,我们来实际看一下初始化的过程。

// Include/internal/pycore_runtime_init.h
#define _PyRuntimeState_INIT(runtime) \
    { \
     /* ... */ \
        .static_objects = { \
            .singletons = { \
                .small_ints = _Py_small_ints_INIT, \
                .bytes_empty = _PyBytes_SIMPLE_INIT(0, 0), \
                .bytes_characters = _Py_bytes_characters_INIT, \
            }, \
        }, \
        ._main_interpreter = \
            _PyInterpreterState_INIT(runtime._main_interpreter), \
    }

runtime 保存了程序运行时的环境和行为,比如内存分配、异常处理、GIL 状态、以及静态单例对象等等,当然还有进程状态对象。