Один и тот же адрес памяти для разных строк в Cython - PullRequest
1 голос
/ 16 мая 2019

Я написал объект дерева в Cython, который имеет много узлов, каждый из которых содержит один символ Unicode.Я хотел проверить, становится ли персонаж интернированным, если я использую Py_UNICODE или str в качестве типа переменной.Я пытаюсь проверить это, создав несколько экземпляров класса узла и получив адрес памяти символа для каждого, но каким-то образом я получаю один и тот же адрес памяти, даже если разные экземпляры содержат разные символы.Вот мой код:

from libc.stdint cimport uintptr_t

cdef class Node():
    cdef:
        public str character
        public unsigned int count
        public Node lo, eq, hi

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

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

Я пытаюсь сравнить места памяти, например, из Python:

a = Node("a")
a2 = Node("a")
b = Node("b")
print(a.memory(), a2.memory(), b.memory())

Но адреса памяти, которые выводятся на печать, одинаковы.Что я делаю не так?

1 Ответ

3 голосов
/ 16 мая 2019

Очевидно, что вы делаете не то, что, как вы думаете, вы делаете.

self.character[0] не возвращает адрес / ссылку первого символа (как это было бы в случае массиванапример), но Py_UCS4 -значение (т. е. присвоенное 32-битное целое число), которое копируется в (локальную, временную) переменную в стеке.

В вашей функции, <uintptr_t>&self.character[0] возвращает вам адрес локальной переменной в стеке, который на каждый шанс всегда один и тот же, потому что при вызове memory всегда существует один и тот же макет стека.

Чтобы сделать его более понятным, здесьразница с char * c_string, где &c_string[0] дает вам адрес первого символа в c_string.

Сравнить:

%%cython
from libc.stdint cimport uintptr_t

cdef char *c_string = "name";
def get_addresses_from_chars():
    for i in range(4):
        print(<uintptr_t>&c_string[i])

cdef str py_string="name";
def get_addresses_from_pystr():
    for i in range(4):
        print(<uintptr_t>&py_string[i])

Сейчас:

>>> get_addresses_from_chars() # works  - different addresses every time
# ...7752
# ...7753
# ...7754
# ...7755
>>> get_addresses_from_pystr() # works differently - the same address.
# ...0672 
# ...0672
# ...0672
# ...0672

Вы можете видеть это так: c_string[...] это cdefфункциональность, но py_string[...] является Python-функциональностью и поэтому не может возвращать адрес для каждой конструкции.

Чтобы повлиять на макет стека, вы можете использовать рекурсивную функцию:

def memory(self, level):
    if level==0 :
        return <uintptr_t>&self.character[0]
    else:
        return self.memory(level-1)

Теперьвызов его с помощью a.memory(0), a.memory(1) и т. д. даст вам другие адреса (если только не начнется оптимизация хвостового вызова, я не верю, что это произойдет, но вы можете отключить оптимизацию (-O0) просточтобы быть уверенным).Поскольку в зависимости от level / recursion-глубина локальная переменная, адрес которой будет возвращен, находится в другом месте в стеке.


Чтобы увидеть, интернированы ли объекты Unicode, онадостаточно использовать id, который возвращает адрес объекта (это детали реализации CPython), поэтому вам вообще не нужен Cython:

>>> id(a.character) == id(a2.character)
# True

или в Cython, делая то же самое, чтоid делает (немного быстрее):

%%cython
from libc.stdint cimport uintptr_t
from cpython cimport PyObject
...
    def memory(self):
        # cast from object to PyObject, so the address can be used
        return <uintptr_t>(<PyObject*>self.character)

Вам необходимо привести object к PyObject *, чтобы Cython позволил получить адрес переменной.

А теперь:

 >>> ...
 >>> print(a.memory(), a2.memory(), b.memory())
 # ...5800 ...5800 ...5000

Если вы хотите получить адрес первой кодовой точки в объекте Unicode (который не совпадает с адресом строки), вы можетеиспользуйте <PY_UNICODE *>self.character, который Cython заменит при вызове PyUnicode_AsUnicode, например:

%%cython
...   
def memory(self):
    return <uintptr_t>(<Py_UNICODE*>self.character), id(self.character)

и теперь

>>> ...
>>> print(a.memory(), a2.memory(), b.memory())
# (...768, ...800) (...768, ...800) (...144, ...000)

т.е. "a" интернировани имеет адрес, отличный от "b", а буфер кодовых точек имеет адрес, отличный от объектов, содержащих его (как и следовало ожидать).

...