31

我一直在尝试了解如何在场景下实现 CPython。Python 是高级别的很好,但我不喜欢把它当作一个黑盒子。

考虑到这一点,元组是如何实现的?我已经查看了源 (tupleobject.c),但它超出了我的想象。

我看到了PyTuple_MAXSAVESIZE = 20PyTuple_MAXFREELIST = 2000什么是保存和“免费列表”?(长度为 20/21 或 2000/2001 的元组之间会有性能差异吗?是什么强制最大元组长度?)

4

2 回答 2

39

需要注意的是,此答​​案中的所有内容均基于我从查看您链接的实现中收集到的内容。

元组的标准实现似乎只是一个数组。但是,有很多优化可以加快速度。

首先,如果您尝试创建一个空元组,CPython 将返回一个代表空元组的规范对象。因此,它可以节省大量仅分配单个对象的分配。

接下来,为了避免分配一堆小对象,CPython 为许多小列表回收内存。有一个固定常数 ( PyTuple_MAXSAVESIZE) 使得所有小于此长度的元组都有资格回收它们的空间。每当一个长度小于该常量的对象被释放时,与其关联的内存有可能不会被释放,而是将根据其大小存储在“空闲列表”中(下一段中将详细介绍) . 这样,如果您需要分配一个大小为 n 的元组,而其中一个已被分配且不再使用,CPython 可以回收旧数组。

空闲列表本身被实现为一个大小数组,PyTuple_MAXSAVESIZE存储指向未使用元组的指针,其中数组的第 n 个元素指向 NULL(如果没有大小为 n 的额外元组可用)或指向一个大小为 n 的回收元组。如果有多个不同的大小为 n 的元组可以重用,则它们通过将每个元组的第零个入口点指向下一个可以重用的元组,以一种链表的形式链接在一起。(由于只分配了一个长度为零的元组,因此永远不会有读取不存在的第零元素的风险)。通过这种方式,分配器可以存储一定数量的每个大小的元组以供重用。为了确保这不会使用太多内存,还有第二个常量PyTuple_MAXFREELIST控制任何桶内任何这些链表的最大长度。然后有一个二级长度数组,PyTuple_MAXSAVESIZE用于存储每个给定长度的元组的链表长度,这样就不会超过这个上限。

总而言之,这是一个非常聪明的实现!

于 2013-01-03T09:14:26.947 回答
38

因为在正常操作过程中 Python 会创建和销毁大量的小元组,Python 会为此目的保留一个小元组的内部缓存。这有助于减少大量的内存分配和释放流失。出于同样的原因,从 -5 到 255 的小整数被保留(制成单例)。

PyTuple_MAXSAVESIZE定义控制符合此优化条件的元组的最大大小,定义PyTuple_MAXFREELIST控制这些元组中有多少保留在内存中。当长度为 < 的元组PyTuple_MAXSAVESIZE被丢弃时,如果仍有空间(in tupledealloc)将其添加到空闲列表中,以便在 Python 创建新的小元组(in )时重新使用PyTuple_New

Python 在如何存储这些方面有点聪明;对于长度大于 0 的每个元组,它将重用每个缓存元组的第一个元素,将PyTuple_MAXFREELIST元组链接到一个链表中。所以free_list数组中的每个元素都是一个 Python 元组对象的链表,并且这样一个链表中的所有元组都是相同大小的。唯一的例外是空元组(长度为 0);这些中只需要一个,它是一个单例。

所以,是的,对于超过长度的元组,PyTuple_MAXSAVESIZEpython 保证必须为新的 C 结构单独分配内存,如果你创建丢弃这样的元组很多,这可能会影响性能。

如果你想了解 Python C 的内部结构,我建议你学习Python C API;它将更容易理解 Python 用于在 C 中定义对象、函数和方法的各种结构。

于 2013-01-03T09:12:21.887 回答