![]() |
痴情的饼干 · 基于全景照片和场景模型计算导出纹理贴图(思路 ...· 1 年前 · |
![]() |
骑白马的西红柿 · 为什么Python要使用有明显缺陷的引用计数 ...· 1 年前 · |
![]() |
沉着的苦咖啡 · python - Psycopg2 ...· 1 年前 · |
![]() |
很拉风的香菜 · Java 解析 带 T Z 的 UTC ...· 1 年前 · |
tensor |
https://cloud.tencent.com/developer/article/2066974?from=article.detail.1900541&areaSource=106000.8&traceId=4L2sYJoSFVtVCxHq1klfH |
![]() |
坚韧的熊猫
1 年前 |
numpy 和 pytorch tensor 存在内存是否连续的情况,对运行速度甚至网络运行结果都存在影响。
contiguous array
,指的是数组在内存中存放的地址也是连续的(注意内存地址实际是一维的),即访问数组中的下一个元素,直接移动到内存中的下一个地址就可以。
行是指多维数组一维展开的方式,对应的是列优先。C/C++中使用的是行优先方式(row major),Matlab、Fortran使用的是列优先方式(column major),PyTorch中Tensor底层实现是C,也是使用行优先顺序,因此也称为
C order
。
而
Fortran Order
则指的是列优先的顺序(Column-major Order),即内存中同列的元素存在一起。
考虑一个2维数组
arr = np.arange(12).reshape(3,4)
。这个数组看起来结构是这样的:
在计算机的内存里,数组
arr
实际存储是像下图所示的:
这意味着
arr
是
C连续的
(
C contiguous
)的,因为在内存是行优先的,即某个元素在内存中的下一个位置存储的是它同行的下一个值。
如果想要向下移动一列,则只需要跳过3个块既可(例如,从0到4只需要跳过1,2和3)。
import numpy as np
if __name__ == '__main__':
arr = np.arange(12).reshape(3, 4)
print(arr.flags)
print(arr)
pass
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
也就是其中的行 [ 0 1 2 3] 在内存中连续,那么 [0 4 8] 就不会连续了,因此 F_CONTIGUOUS 为 False
上述数组的转置
arr.T
则没有了C连续特性,因为同一行中的相邻元素现在并不是在内存中相邻存储的了:
这里要说明一下,如果直接用这些值创建的numpy变量是连续的,因为Python默认 C order,新创建的numpy都是行优先的 但是我们创建arr时是以 0 - 11 为顺序创建的,其中[0 1 2 3] [4 5 6 7] [8 9 10 11]连续,矩阵转置后只改变引用,内存数据并不发生变化 类似的操作如numpy 的
slice
、transpose
、转置
或 tensor中的permute
等操作都可能导致改变之前数据与内存的行连续状况
import numpy as np
if __name__ == '__main__':
arr = np.arange(12).reshape(3, 4)
arr = arr.T
print(arr.flags)
print(arr)
pass
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
data.contiguous
、
data.c_contiguous
、
data.f_contiguous
属性
flags
属性
import numpy as np
if __name__ == '__main__':
arr = np.arange(12).reshape(3, 4)
print(arr.data.contiguous) # True
print(arr.data.c_contiguous) # True
print(arr.data.f_contiguous) # False
print(arr.flags)
print(arr)
True
False
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
is_contiguous
用来查看是否 C 连续
import torch
import numpy as np
if __name__ == '__main__':
arr = np.arange(12).reshape(3, 4)
ten = torch.from_numpy(arr)
print(ten.is_contiguous()) # True
ten_t = torch.from_numpy(arr.T)
print(ten_t.is_contiguous()) # False
True
False
从性能上来说,获取内存中相邻的地址比不相邻的地址速度要快很多(从RAM读取一个数值的时候可以连着一起读一块地址中的数值,并且可以保存在Cache中),这意味着对连续数组的操作会快很多。
由于
arr
是C连续的,因此对其进行行操作比进行列操作速度要快
np.sum(arr, axis=1) # 按行求和
会比
np.sum(arr, axis=0) # 按列求和
稍微快些。 同理,在
arr.T
上,列操作比行操作会快些。
其实写这篇博客的原因,就是我的onnx模型对于完全相同数据的tensor产生了完全不同的表现,险些三观俱碎。挣扎了几个小时后发现原来是数据的连续性在作祟。
平台 |
影响 |
---|---|
numpy |
计算不连续的变量,结果不会受到影响 |
pytorch |
输入不连续的tensor,结果不会受到影响 |
onnx |
输入不连续的tensor,结果直接爆炸 |
view
方法时会报错:
invalid argument 2: view size is not compatible with input tensor's size and stride (at least one dimension
spans across two contiguous subspaces). Call .contiguous() before .view().
ValueError: some of the strides of a given numpy array are negative.
This is currently not supported, but will be added in future releases.
变量可以通过重新开辟空间,将数据连续拷贝进去的方法将不连续的数据变成某种连续方式。
np.ascontiguousarray(arr)
变为C连续,
np.asfortranarray(arr)
变为Fortran连续
import numpy as np
if __name__ == '__main__':
arr = np.arange(12).reshape(3, 2, 2)
print(arr.data.c_contiguous) # True arr C连续
tran_arr = arr.transpose(2, 0, 1)
print(tran_arr.data.contiguous) # False tran_arr不连续
c_arr = np.ascontiguousarray(tran_arr) # 变为 C 连续
print(c_arr.flags)
f_arr = np.asfortranarray(tran_arr) # 变为 Fortran 连续
print(f_arr.flags)
True
False
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
contiguous()
import torch
import numpy as np
if __name__ == '__main__':
arr = np.arange(12).reshape(3, 2, 2)
tran_arr = arr.transpose(2, 0, 1)
print(tran_arr.data.contiguous) # False tran_arr不连续
ten = torch.from_numpy(tran_arr)
print(ten.is_contiguous()) # False 不连续的numpy产生不连续的tensor
c_ten = ten.contiguous()
print(c_ten.is_contiguous()) # True Tensor变成连续的
c_ten_arr = c_ten.numpy()
print(c_ten_arr.flags) # tensor 的连续函数结果是变成C连续的变量
False
False
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : True
ALIGNED : True