gpt4 book ai didi

python - cython 中 str 和 Py_UNICODE 的驻留和内存地址

转载 作者:行者123 更新时间:2023-12-01 00:56:43 25 4
gpt4 key购买 nike

上下文:我构建了一个树数据结构,在 cython 的节点中存储单个字符。现在我想知道如果我实习所有这些角色是否可以节省内存。以及我是否应该使用 Py_UNICODE 作为变量类型或常规 str。这是我使用 Py_UNICODE 的精简 Node 对象:

from libc.stdint cimport uintptr_t
from cpython cimport PyObject

cdef class Node():
cdef:
public Py_UNICODE character

def __init__(self, Py_UNICODE character):
self.character = character

def memory(self):
return <uintptr_t>&self.character

首先尝试查看字符是否被自动保留。如果我在 Python 中导入该类并创建具有不同或相同字符的多个对象,这些是我得到的结果:

a = Node("a")
a_py = a.character
a2 = Node("a")
b = Node("b")

print(a.memory(), a2.memory(), b.memory())
# 140532544296704 140532548558776 140532544296488

print(id(a.character), id(a2.character), id(b.character), id(a_py))
# 140532923573504 140532923573504 140532923840528 140532923573504

因此,我得出的结论是 Py_UNICODE 不会自动实习,并且在 python 中使用 id() 不会给我实际的内存地址,而是一个副本的地址(我想 python 会自动实习单个 unicode 字符,然后只是将其内存地址返回给我)。

接下来我尝试在使用 str 时做同样的事情。只需将 Py_UNICODE 替换为 str didn't work ,这就是我现在尝试执行的操作:

%%cython
from libc.stdint cimport uintptr_t
from cpython cimport PyObject

cdef class Node():
cdef:
public str character

def __init__(self, str character):
self.character = character

def memory(self):
return <uintptr_t>(<PyObject*>self.character)

这些是我得到的结果:

...
print(a.memory(), a2.memory(), b.memory())
# 140532923573504 140532923573504 140532923840528

print(id(a.character), id(a2.character), id(b.character), id(a_py))
# 140532923573504 140532923573504 140532923840528 140532923573504
基于此,我首先认为单字符 str 也被驻留在 cython 中,并且 cython 不需要从 python 复制字符,这解释了为什么 id() 和 .memory() 给出相同的地址。但后来我尝试使用更长的字符串得到了相同的结果,从中我可能不想得出结论,更长的字符串也会被自动保留?如果我使用 Py_UNICODE,我的树使用的内存也会更少,因此如果 str 被保留,但 Py_UNICODE 不是,则没有多大意义。有人可以向我解释这种行为吗?我该如何去实习?

(我正在 Jupyter 中对此进行测试,以防产生影响)

编辑:删除了不必要的节点 ID 比较,而不是字符比较。

最佳答案

您这边有误会。 PY_UNICODE 不是一个 python 对象 - 它是一个 typedef for wchar_t .

只有字符串对象(至少其中一些)会被保留,而不是 wchar_t 类型的简单 C 变量(或者事实上任何 C 类型)。这也不是没有任何意义:wchar_t 很可能是 32 位大,而保留指向内部对象的指针则需要 64 位。

因此,只要 self 不同,变量 self.character(类型为 PY_UNICODE)的内存地址就永远不会相同对象(无论 self.character 具有哪个值)。

另一方面,当您在纯 python 中调用 a.character 时,Cython 知道该变量不是简单的 32 位整数,并会自动转换它(character是产权吗?)通过 PyUnicode_FromOrdinal 到 unicode 对象。返回的字符串(即 a_py)可能是“interned”,也可能不是。

当该字符的代码点 is less than 256 (即 latin1),它得到 kind of interned - 否则不会。仅包含一个字符的前 256 个 unicode 对象具有 special place - 不是the same as other interned strings (因此在上一节中使用了“interned”)。

考虑:

>>> a="\u00ff" # ord(a) =  255
>>> b="\u00ff"
>>> a is b
# True

但是

>>> a="\u0100" # ord(a) =  256
>>> b="\u0100"
>>> a is b
# False
<小时/>

这里的关键是:使用 PY_UNICODE - 即使没有实习,它也比实习字符串/unicode对象更便宜(4字节)(8字节用于引用+一旦内存用于内部对象)并且比非内部对象便宜得多(这可能发生)。

或者更好,正如@user2357112所指出的,使用Py_UCS4确保 4 字节的大小得到保证(需要能够支持所有可能的 unicode 字符) - wchar_t 可以小至 1 字节(即使这在现在可能很不寻常) )。如果您对所使用的字符了解更多,则可以回退到 Py_UCS2Py_UCS1

<小时/>

但是,当使用 Py_UCS2Py_USC1 时,必须考虑到,Cython 将不支持与 Py_UCS4 的情况一样从/到 unicode 的转换 (或已弃用的 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)

还应该确保使用 Py_USC2 确实节省了内存:CPython 使用 pymalloc它具有 8 个字节的对齐方式,这意味着例如的对象20字节仍将使用24字节(3*8)内存。另一个问题是来自 C 编译器的结构的对齐,对于

struct A{
long long int a;
long long int b;
char ch;
};

sizeof(A) 是 24,而不是 17(请参阅 live)。

如果确实在这两个字节之后,那么还有更大的鱼要炸:不要将节点设为 Python 对象,因为它会带来 16 个字节的开销,用于不需要的多态性和引用计数 - 这意味着整个数据结构应该用C编写,整个wrappend用Pyt​​hon编写。然而,这里还要确保以正确的方式分配内存:通常的 C 运行时内存分配器具有 32 或 64 字节对齐,即分配较小的大小仍然会导致使用 32/64 字节。

关于python - cython 中 str 和 Py_UNICODE 的驻留和内存地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56192032/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com