Есть недоразумение с вашей стороны.PY_UNICODE
это не объект Python - это typedef для wchar_t
.
Только интернированные строковые объекты (по крайней мере, некоторые из них), но не простые C-переменные типа wchar_t
(или фактически любого C-типа).Это также не имеет никакого смысла: wchar_t
, скорее всего, имеет размер 32 бита, в то время как указатель на интернированный объект будет стоить 64 бита.
Таким образом, адрес памяти переменной self.character
(типа PY_UNICODE
) никогда не бывает одинаковым до тех пор, пока self
являются различными объектами (независимо от того, какое значение имеет self.character
).
С другой стороны, когда вы вызываете a.character
в чистом Python, Cython знает, что переменная не является простым 32-битным целым числом и автоматически преобразует его (character
это свойство правильно?) В unicode-объект через PyUnicode_FromOrdinal
.Возвращенная строка (т. Е. a_py
) может быть «интернирована» или нет.
Когда кодовая точка этого символа меньше 256 (т. Е. Latin1), он получает вид интернированного - иначе нет.Первые 256 unicode-объектов, состоящих только из одного символа, имеют специальное место - не , как другие интернированные строки (таким образом, использование "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
- даже дешевле (4 байта)если не интернировано, чем интернированные строки / юникод-объекты (8 байт для справки + один раз память для интернированного объекта) и намного дешевле, чем не интернированные объекты (что может случиться).*, как указал @ user2357112, используйте Py_UCS4
, чтобы гарантировать гарантированный размер 4 байта (который необходим для поддержки всех возможных символов Юникода) - wchar_t
может бытьвсего 1 байт (даже если это, вероятно, довольно необычно в наши дни).Если вы знаете больше об используемых символах, вы можете вернуться к Py_UCS2
или Py_UCS1
.
Однако при использовании Py_UCS2
или Py_USC1
нужно учитывать, что Cythonне будет поддерживать преобразование из / в 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)
Также следует сделатьУбедитесь, что использование 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 байтов для не очень необходимого полиморфизма и подсчета ссылок - это означает, что вся структура данных должна быть записанаС и завернуть в целом в Python.Однако и здесь обязательно убедитесь, что память выделена правильно: обычные распределители памяти C-runtime имеют выравнивание 32 или 64 байта, т.е. выделение меньших размеров все еще приводит к использованию 32/64 байта.