Python cython中str和Py_UNICODE的内部地址和内存地址
上下文:我构建了一个树数据结构,在cython的节点中存储单个字符。现在我想知道如果我实习所有这些角色是否能节省内存。以及我应该使用Py_UNICODE作为变量类型还是常规str。这是我的精简节点对象,使用Py_UNICODE:Python cython中str和Py_UNICODE的内部地址和内存地址,python,string,memory,cython,string-interning,Python,String,Memory,Cython,String Interning,上下文:我构建了一个树数据结构,在cython的节点中存储单个字符。现在我想知道如果我实习所有这些角色是否能节省内存。以及我应该使用Py_UNICODE作为变量类型还是常规str。这是我的精简节点对象,使用Py_UNICODE: 来自libc.stdint cimport uintptru\t 从cpython cimport PyObject cdef类节点(): cdef: 公共Py_UNICODE字符 def uu init uu(self,Py_UNICODE字符): self.char
来自libc.stdint cimport uintptru\t
从cpython cimport PyObject
cdef类节点():
cdef:
公共Py_UNICODE字符
def uu init uu(self,Py_UNICODE字符):
self.character=字符
def存储器(自):
返回&自我角色
如果第一次尝试查看字符是否自动插入。如果我在Python中导入该类并创建具有不同或相同字符的多个对象,我将得到以下结果:
a=节点(“a”)
a_py=a个字符
a2=节点(“a”)
b=节点(“b”)
打印(a.内存(),a2.内存(),b.内存())
# 140532544296704 140532548558776 140532544296488
打印(id(a字符)、id(a2字符)、id(b字符)、id(a字符))
# 140532923573504 140532923573504 140532923840528 140532923573504
因此,我会得出结论,Py_UNICODE不是自动插入的,在python中使用id()不会给我实际的内存地址,而是一个副本的内存地址(我假设python会自动插入单个UNICODE字符,然后只返回给我该字符的内存地址)
接下来,我尝试在使用str时做同样的事情。简单地用str替换Py_UNICODE,我现在就是这么做的:
%%cython
从libc.stdint cimport uintpr\t
从cpython cimport PyObject
cdef类节点():
cdef:
公共str字符
定义初始化(self,str字符):
self.character=字符
def存储器(自):
返回(self.character)
我得到的结果如下:
。。。
打印(a.内存(),a2.内存(),b.内存())
# 140532923573504 140532923573504 140532923840528
打印(id(a字符)、id(a2字符)、id(b字符)、id(a字符))
# 140532923573504 140532923573504 140532923840528 140532923573504
基于此,我首先认为cython中也包含单字符str,cython不需要从python复制字符,这就解释了为什么id()和.memory()提供相同的地址。但后来我尝试使用更长的字符串,得到了相同的结果,从中我可能不想得出结论,更长的字符串也会自动插入?如果我使用Py_-UNICODE,我的树也会使用更少的内存,因此如果str被拘留,这没有多大意义,但是Py_-UNICODE不是。有人能给我解释一下这种行为吗?那我怎么去实习呢
(我正在Jupyter进行测试,以防有什么不同)
编辑:删除了不必要的节点id比较,而不是字符。您有一个误解
PY_UNICODE
不是python对象,它是一个
只有字符串对象(至少是其中的一些对象)被插入,但不是类型为wchar\u t
的简单C变量(或者实际上是任何C类型的变量)。这也没有任何意义:一个wchar\u t
很可能是32位大,而保持一个指向内部对象的指针需要花费64位
因此,变量self.character
(类型PY_UNICODE
)的内存地址永远不会相同,只要self
是不同的对象(无论self.character
具有哪个值)
另一方面,在纯python中调用a.character
时,Cython知道该变量不是简单的32位整数,并通过将其自动转换为unicode对象(character
is property right?)。返回的字符串(即a_py
)可能是“interned”或不是
当此字符的代码点(即latin1)为空时,它将获得-否则不会。前256个仅由一个字符组成的unicode对象具有-not(因此在上一节中使用了“interned”)
考虑:
>>> a="\u00ff" # ord(a) = 255
>>> b="\u00ff"
>>> a is b
# True
但是
关键在于:使用
PY_UNICODE
——即使没有插入,也比插入字符串/UNICODE对象便宜(4字节)(8字节用于引用+一次插入对象的内存)而且比不插入对象便宜得多(这可能发生)
或者,更好,正如@user2357112所指出的,用于确保保证4个字节的大小(这是能够支持所有可能的unicode字符所必需的)wchar\u t
可以小到1个字节(即使这在当今可能非常罕见)。如果您对所使用的字符了解更多,您可以回到Py_UCS2
或Py_UCS1
但是,当使用
Py_UCS2
或Py_USC1
时,必须考虑到Cython不支持从unicode到unicode的转换,如Py_UCS4
(或弃用的Py_unicode
)的情况,并且必须手动完成,例如:
%%cython
from libc.stdint cimport uint16_t
# need to wrap typedef as Cython doesn't do it
cdef extern from "Python.h":
ctypedef uint16_t Py_UCS2
cdef class Node:
cdef:
Py_UCS2 character_
@property
def character(self):
# cython will do the right thing for Py_USC4
return <Py_UCS4>(self.character_)
def __init__(self, str character):
# unicode -> Py_UCS4 managed by Cython
# Py_UCS4 -> Py_UCS2 is a simple C-cast
self.character_ = <Py_UCS2><Py_UCS4>(character)
sizeof(A)
是24而不是17(请参阅)
如果有人真的在追求这两个字节,那么还有一条更大的鱼要钓:不要让节点成为Python对象,因为这会为不需要的多态性和引用计数带来16个字节的开销——这意味着整个数据结构都应该用C编写,并在Python中作为一个整体进行包装。然而,这里也要确保以正确的方式分配内存:通常的C运行时内存分配器有32或64字节对齐,即分配较小的大小仍然会导致使用32/64字节。显然是的。这是漫长的一天。移除它。(:虽然
PY_UNICODE
更便宜,但仍然不应该使用它。它已被弃用,并且它的大小不总是足以容纳所有字符。您不希望您的应用程序因为有人试图使用鸡蛋而崩溃
%%cython
from libc.stdint cimport uint16_t
# need to wrap typedef as Cython doesn't do it
cdef extern from "Python.h":
ctypedef uint16_t Py_UCS2
cdef class Node:
cdef:
Py_UCS2 character_
@property
def character(self):
# cython will do the right thing for Py_USC4
return <Py_UCS4>(self.character_)
def __init__(self, str character):
# unicode -> Py_UCS4 managed by Cython
# Py_UCS4 -> Py_UCS2 is a simple C-cast
self.character_ = <Py_UCS2><Py_UCS4>(character)
struct A{
long long int a;
long long int b;
char ch;
};