python与C/C++数据交互的陷阱:numpy/torch中的视图
前言
最近碰到了一个bug,调试了两天。最后发现是数据存储排列方式的问题,由此忽然想到pytorch或者numpy中tensor(或者ndarray)里面视图的概念。
视图
- 视图是数据的一个别称或引用,通过该别称或引用亦便可访问、操作原有数据,但原有数据不会产生拷贝。如果我们对视图进行修改,它会影响到原始数据,物理内存在同一位置。
- 副本是一个数据的完整的拷贝,如果我们对副本进行修改,它不会影响到原始数据,物理内存不在同一位置。
视图一般发生在:
- numpy的切片操作返回原数据的视图。
- 调用 ndarray 的
view()
函数产生一个视图。 - 调用
transpose
,.T
- 调用
np.split
- 调用其他不会发生内存copy的函数
特别的,如果对于 contiguous = False 的数据,直接对其.shape
赋值会报错,调用reshape
操作会发生copy操作。
副本一般发生在:
- Python 序列的切片操作,调用deepCopy()函数。
- 调用 ndarray 的 copy() 函数产生一个副本。
bug复现
既然创建视图不会发生内存复制,那对于所有拥有不同视图却拥有相同数据存储地址的ndarray或者tensor来说,指向的空间是一样的,这样python中数据传入c/c++时读取的实际上还是原数据。
比如针对于tensor A,现在想把A.T利用ctypes或者cython传给c程序,直接传入A.T
就会产生bug,而且不易查找。
这里ravel函数的order参数解释如下 order{‘C’,’F’, ‘A’, ‘K’}, optional The elements of a are read using this index order. ‘C’ means to index the elements in row-major, C-style order, with the last axis index changing fastest, back to the first axis index changing slowest. ‘F’ means to index the elements in column-major, Fortran-style order, with the first index changing fastest, and the last index changing slowest. Note that the ‘C’ and ‘F’ options take no account of the memory layout of the underlying array, and only refer to the order of axis indexing. ‘A’ means to read the elements in Fortran-like index order if a is Fortran contiguous in memory, C-like order otherwise. ‘K’ means to read the elements in the order they occur in memory, except for reversing the data when strides are negative. By default, ‘C’ index order is used. 所以尽管我们的目的是想给C/C++函数传入A的转置,但传入的依然是A。
A.T
仅仅是创建了一个新的视图,并且其数据也不再是contiguous
,另外打印A.T.flags
会发现新的视图变成了列连续。(C代表C语言格式存储,行优先,F代表Fortran 表示列优先) 那么该如何解决呢? 实际上解决思路很简单,那就是将按照数据的视图重新复制一遍数据,将其变为
contiguous
的。即传入A的转置时调用np.ascontiguous(A.T)
即可将其数据copy一边并设置为行优先存储。
小结
这个bug实在隐蔽,我对计算图的某个模块一层一层的打印和校对才找到,解决问题后分析原因写下这篇文章避免以后再出错。
创建日期: September 17, 2024