【问题标题】:Address of memoryviews in cython are the same but point to different objectcython中memoryviews的地址相同但指向不同的对象
【发布时间】:2019-05-13 11:00:37
【问题描述】:

问题

在 cython 中定义不同的对象时,memoryviews 将返回相同的地址。但是,数组本身在被索引时会被修改。

背景。

我有用 cython 编写的基类和派生类。我注意到,当我对类应用多处理时,底层缓冲区在不同的进程中发生了更改,这不是预期的。在酸洗过程中,我编写了一个简单的 __reduce__ 方法和 __deepcopy__ 重建原始对象的方法。为了清楚起见,我降低了下面代码的复杂性。现在我的问题是,为什么内存视图返回相同的地址?此外,为什么即使 memoryview 相同,numpy 数组本身也会正确更改

#distutils: language=c++
import numpy as np
cimport numpy as np
cdef class Temp:
    cdef double[::1] inp
    def __init__(self, inp):
        print(f'id of inp = {id(inp)}')
        self.inp = inp

cdef np.ndarray x = np.ones(10)
cdef Temp a       = Temp(x)
cdef Temp b       = Temp(x)
cdef Temp c       = Temp(x.copy())
b.inp[0] = -1
c.inp[2] = 10
print(f'id of a.inp = {id(a.inp)}\nid of b.inp = {id(b.inp))}\nid of c.inp = {id(c.inp)}')
print(f'id of a.inp.base = {id(a.inp.base)}\nid of b.inp.base = {id(b.inp.base))}\nid of c.inp.base = {id(c.inp.base)}')

print('a.inp.base',a.inp.base)
print('b.inp.base',b.inp.base) # expected to be the same as a
print('c.inp.base',c.inp.base) # expected to be different to a/b

输出:

id of inp = 139662709551872
id of inp = 139662709551872
id of inp = 139662709551952
id of a.inp = 139662450248672
id of b.inp = 139662450248672
id of c.inp = 139662450248672
id of a.inp.base = 139662709551872
id of b.inp.base = 139662709551872
id of c.inp.base = 139662709551952
a.inp.base [-1.  1.  1.  1.  1.  1.  1.  1.  1.  1.]
b.inp.base [-1.  1.  1.  1.  1.  1.  1.  1.  1.  1.]
c.inp.base [ 1.  1. 10.  1.  1.  1.  1.  1.  1.  1.]

【问题讨论】:

    标签: python-3.x numpy cython typed-memory-views


    【解决方案1】:

    我们所说的类型化内存视图并不是一个单一的类:根据上下文(Cython 代码、纯 Python 代码),它会在底层改变其身份。

    让我们开始吧

    %%cython 
    cdef class Temp:
        cdef double[::1] inp
    

    这里的double[::1] inp__Pyx_memviewslice 类型,它不是Python 对象:

    typedef struct {
      struct {{memview_struct_name}} *memview;
      char *data;
      Py_ssize_t shape[{{max_dims}}];
      Py_ssize_t strides[{{max_dims}}];
      Py_ssize_t suboffsets[{{max_dims}}];
    } {{memviewslice_name}};
    

    当我们调用id(self.inp) 时会发生什么?显然,id 是一个纯 Python 函数,因此必须从 self.inp 创建一个新的临时 Python 对象(内存视图)(只能调用 id),然后直接销毁。临时 Python 对象的创建是通过 __pyx_memoryview_fromslice 完成的。

    知道这很容易解释为什么 id 是相等的:尽管是不同的对象,但临时 memoryview 巧合地具有相同的地址(因此相同的 id,这是 CPython 的实现细节),因为内存被 CPython 一遍又一遍地重用。

    在 Python 中到处都有类似的场景,这里是an example for method-objects,或者更简单的一个:

    class A:
        pass
    # the life times of temporary objects don't overlap, so the ids can be the equal
    id(A())==id(A())
    # output: True
    
    # the life times of objects overlap, so the id cannot be equal 
    a,b=A(), A()
    id(a)==id(b)
    # output: False
    

    简而言之:您的期望是,相同的id 意味着相同的对象是错误的。只有当对象的生命周期重叠时,这个假设才成立。

    【讨论】:

    • 有趣。这解释了我对您的第一个示例的观察。不知道那个内存实现。无论如何将这些 memviews 绑定到 cdef 类?例如使用 cython.binding 指令?再次感谢您的回答!
    • 澄清一下,链接可能不是问题,而且您说它们没有时间重叠,但由于这些类将在单独的进程中运行,它们可能有时间重叠。因此,我怀疑 memviews(如果它们指向相同的地址)会覆盖数据。这不是故意的。根据我从模拟中得到的结果,情况似乎就是这样。我可以将它们转换为适当的 ndarray,但不幸的是,与单核运行时间相比,这会将性能降低 1/3。
    • @ead 的意思(我认为)是,当您调用 id(memview) 时,它会创建一个临时 Python 对象只是为了显示“地址”,而这是打印出来的。就更改了哪些数据而言,id(memview.base) 是最重要的,它的行为符合您的预期。
    • @GlobalTraveler 对不起,但我真的明白你的担心:即使 memview 本身具有相同的地址,它们指向不同的缓冲区。
    • 抱歉我缺乏知识(python 开发人员开始编译)。我有这个用 python 编写的模拟代码,现在转换为具有正确类型的 cdef 类。但是,它使用 memview 来快速访问模拟状态。但是,当我使用 multiproccessing.Pool 并行运行这些时,我注意到当在不同进程中访问缓冲区时,它们都会发生变化,这意味着(a)我的 pickle/unpickle 方法无法按预期工作或(b)缓冲区仍然指向同一个对象。一种可行的替代方法是在每个过程中重建。
    猜你喜欢
    • 2016-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-26
    • 2015-06-09
    相关资源
    最近更新 更多