关于SpaCy中Doc对象删除后仍可用的内存机制及大小差异的问询
关于spaCy Doc、Token和Span的内存机制疑问解答
官方文档说明:A Doc is a sequence of Token objects. Access sentences and named entities, export annotations to numpy arrays, losslessly serialize to compressed binary strings. The Doc object holds an array of TokenC structs. The Python-level Token and Span objects are views of this array, i.e. they don't own the data themselves.
这一逻辑虽有合理性,但我好奇其底层具体实现机制,尤其是如下示例所示:即使删除Doc对象(或至少是指向它的变量),它仍能正常工作。
代码示例
import spacy nlp = spacy.load('en_core_web_sm') from sys import getsizeof doc = nlp('King Henry VIII married six times.') print(doc) print(getsizeof(doc)) token = doc[0] print(token) print(getsizeof(token)) span = doc[:3] del doc span.merge() # This updates the vestigial doc despite deletion. print(token) print(getsizeof(token)) # Same size as before, being just a pointer. print(token.doc) # Doc can be retrieved. print(getsizeof(token.doc))
输出结果
King Henry VIII married six times. 184 King 80 King Henry VIII 80 King Henry VIII married six times. 184
我的疑问
鉴于我仅具备Python基础知识,想咨询:
- Doc对象究竟在内存中如何存储,才能实现上述删除后仍可工作的功能?
- 若token变量仅占80字节就能调用Doc对象的全部功能,为何doc变量的大小达到184字节,是其两倍多?
问题解答
1. Doc对象的内存存储与删除后仍可工作的原因
核心要结合Python的引用计数机制和spaCy的底层设计来看:
- spaCy的
Doc核心数据(比如TokenC结构体数组、文本内容、标注信息)是存在C语言层面的内存区域里的,Python层面的Doc、Token、Span都只是指向这个C层数据的"视图包装器",本身不持有实际数据。 - 当你创建
token = doc[0]和span = doc[:3]时,这两个对象会在内部保留对底层C层Doc数据的引用——注意,是直接指向C数据,不是指向Python的doc变量。 - 执行
del doc只是删掉了Python层面指向C层数据的一个引用,但token和span还在引用着底层的C数据,Python的垃圾回收机制不会释放还有引用的内存。所以span.merge()能正常操作底层的Doc数据,token.doc也能通过Token内部保存的指针重新生成Python的Doc对象。
简单说:你删的只是Python层面的一个"标签",真正的核心数据因为还有其他"标签"(Token、Span)指着它,所以没被回收。
2. Token和Doc的大小差异原因
getsizeof()返回的是Python对象本身的内存大小,不包含它指向的底层数据:
Token是轻量级包装器,只需要保存两个关键信息:指向底层C层Doc数据的指针,以及这个Token在Doc数组里的索引位置。这些内容占用的内存很小,所以显示80字节(这个数值会因Python版本、系统架构略有不同)。- 而
Doc作为顶层包装器,需要保存更多内容:除了指向C层数据的指针,还有Python层面的缓存(比如句子分割结果、命名实体缓存)、元数据,以及和Python运行时交互的额外结构。这些额外内容让它的内存占用比Token大,所以显示184字节。
要注意:这两个大小都不包含底层C层存储的实际文本和标注数据,那部分内存是单独计算的,getsizeof()不会统计到。
内容的提问来源于stack exchange,提问作者bongbang
相关产品推荐
相关产品推荐

