Python 为什么使用numpy.save从数组中保存切片的速度较慢,具体取决于切片的方向?

Python 为什么使用numpy.save从数组中保存切片的速度较慢,具体取决于切片的方向?,python,arrays,numpy,slice,Python,Arrays,Numpy,Slice,我有三维数据体(x、y、z),我想保存二维切片(xy、yz、xz平面)并保存它们以备将来使用 我尝试这样做的方法是使用一个函数(slice\u data)获取切片,另一个函数(save\u slices)调用slice\u data,然后使用numpy.save保存切片 如果不保存切片,则无论是否提取xy、yz、xz平面,获取切片的时间都是相似的。 但是,如果保存切片,则保存切片的时间取决于切片的方向,并且对于xy、yz、xz平面不同 为什么呢?使用我的全部数据,这种差异从几分钟到几小时 我在下

我有三维数据体(x、y、z),我想保存二维切片(xy、yz、xz平面)并保存它们以备将来使用

我尝试这样做的方法是使用一个函数(
slice\u data
)获取切片,另一个函数(
save\u slices
)调用
slice\u data
,然后使用
numpy.save
保存切片

如果不保存切片,则无论是否提取xy、yz、xz平面,获取切片的时间都是相似的。 但是,如果保存切片,则保存切片的时间取决于切片的方向,并且对于xy、yz、xz平面不同

为什么呢?使用我的全部数据,这种差异从几分钟到几小时

我在下面编写了一个伪代码,类似于我在完整数据集中用来演示问题的代码。两者之间的时间差如下所示:

不是保存,只是切片

mean time x-slice: 0.00011536836624145507 sec
mean time y-slice: 0.00011417627334594726 sec
mean time z-slice: 0.00011371374130249023 sec
切片和保存:

mean time x-slice: 0.04629791975021362 sec
mean time y-slice: 0.06096100091934204 sec
mean time z-slice: 0.08996494293212891 sec
代码:

导入操作系统
将numpy作为np导入
导入时间
将matplotlib.pyplot作为plt导入
#获取数据的一部分
def切片数据(roi):
dic={}
数据=np.零((512512256))
dic['data']=np.Squence(数据[roi[0]:roi[1]+1,roi[2]:roi[3]+1,roi[4]:roi[5]+1])
返回dic
#如果要保存数据,请保存切片
def save_切片(roi,save=False):
var=‘数据’
对于范围(0,6)内的i:
#迭代以模拟数据的时间序列
a=切片数据(roi)[var]
var_dir='save_test/'
如果不存在os.path.exists(var\u dir):os.makedirs(var\u dir)
file=var_dir+'{0:04d}{1}'。格式(i,'.npy')
如果“保存”为True:
np.save(文件,a)
##定义切片
roix=[256,256,0,512,0,256]#yz平面切片
ROAY=[0,512,256,256,0,256]#xz平面切片
roiz=[0,512,0,512,128,128]#xy平面切片
##计算切片,不保存结果
dtx=[]
dty=[]
dtz=[]
对于范围(100)内的i:
time0=time.time()
保存切片(roix)
time1=time.time()
dtx.append(time1-time0)
time0=time.time()
保存切片(ROY)
time1=time.time()
dty.append(time1-time0)
time0=time.time()
保存切片(roiz)
time1=time.time()
dtz.append(time1-time0)
plt.图(1)
plt.绘图(dtx)
plt.绘图(dty)
平面图(dtz)
plt.title('运行代码而不保存数据的时间')
打印('平均时间x片:{}秒'。格式(np.mean(dtx)))
打印('平均时间y片:{}秒'。格式(np.mean(dty)))
打印('mean time z-slice:{}秒'。格式(np.mean(dtz)))
##计算切片并保存结果
dtx=[]
dty=[]
dtz=[]
对于范围(100)内的i:
time0=time.time()
保存切片(roix,保存=真)
time1=time.time()
dtx.append(time1-time0)
time0=time.time()
保存切片(ROYY,保存=真)
time1=time.time()
dty.append(time1-time0)
time0=time.time()
保存切片(roiz,save=True)
time1=time.time()
dtz.append(time1-time0)
plt.图(2)
plt.绘图(dtx)
plt.绘图(dty)
平面图(dtz)
plt.title('运行代码和保存数据的时间')
打印('平均时间x片:{}秒'。格式(np.mean(dtx)))
打印('平均时间y片:{}秒'。格式(np.mean(dty)))
打印('mean time z-slice:{}秒'。格式(np.mean(dtz)))

原因是Numpy默认情况下按行主顺序存储数据。如果你改变

data = np.zeros((512,512,256))

您将看到保存X片需要花费最长的时间

如果要保存XY平面的多个切片(当您更改为Z坐标时),您将看到更好的性能,方法是将阵列转置并将其复制到该位置,这将确保内存布局符合您的访问模式,从而加快读取(和保存)速度。更详细的解释如下


让我们以以下矩阵为例(来自:

如果这在内存中以行主顺序(numpy行话中的C顺序)表示,则其布局如下:

[1, 2, 3, 4, 5, 6]
[1, 4, 2, 5, 3, 6]
如果矩阵在内存中以列主顺序表示(或F表示Fortran顺序),则其布局如下:

[1, 2, 3, 4, 5, 6]
[1, 4, 2, 5, 3, 6]
现在,如果您使用
m[:,2]
索引到这个数组中,您将得到
[3,6]
,使用
m[1,:]
,您将得到
[4,5,6]
。如果回顾内存布局,您将看到值
[3,6]
在列主表示中是连续的,而
[4,5,6]
在行主表示中是连续的

当从一个数组中读取大量元素时(如保存一个元素时),连续读取这些值的性能要好得多,因为这样可以利用CPU缓存,它比从内存中读取要高1-2个数量级。

简短回答 只有
roix
数组是连续的。因此,使用总线从内存到CPU的传输比不连续数据的传输要快(这是因为总线以块的形式移动数据并缓存它们)

通过使其C连续
np.save(file,np.asarray(a,order='C'))

更多解释 轮廓 您应该使用timeit来计时您的性能,而不是自定义方法

我已经为您做了这些,以展示一个示例:

在一间牢房里,我们得到:

import os
import numpy as np
import time
import matplotlib.pyplot as plt

# take a slice of the data
def slice_data(roi):
    dic = {}
    data = np.zeros((512,512,256))
    dic['data'] = np.squeeze( data[roi[0]:roi[1]+1, roi[2]:roi[3]+1, roi[4]:roi[5]+1] )
    return dic


# save slices if the data
def save_slices(roi, save=False):
    var = 'data'
    for i in range(0,6):
                # iterate to simulate a time series of data
        a = slice_data(roi)[var]
        var_dir = 'save_test/'
        if not os.path.exists(var_dir): os.makedirs(var_dir)
        file = var_dir + '{0:04d}{1}'.format(i,'.npy')

        if save is True:
            np.save(file, a)


## define slices
roix=[256, 256, 0, 512, 0, 256] # yz plane slice
roiy=[0, 512, 256, 256, 0, 256] # xz plane slice
roiz=[0, 512, 0, 512, 128, 128] # xy plane slice
在其他方面:

%%timeit -n 100
save_slices(roix) # 19.8 ms ± 285 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit -n 100
save_slices(roiy) # 20.5 ms ± 948 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit -n 100
save_slices(roiz) # 20 ms ± 345 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
用扑救

%%timeit -n 10 -r 3
save_slices(roix, True) # 32.7 ms ± 2.31 ms per loop (mean ± std. dev. of 3 runs, 10 loops each)

%%timeit -n 10 -r 3
save_slices(roiy, True) # 101 ms ± 2.61 ms per loop (mean ± std. dev. of 3 runs, 10 loops each)

%%timeit -n 10 -r 3
save_slices(roix, True) # 1.9 s ± 21.1 ms per loop (mean ± std. dev. of 3 runs, 10 loops each)
因此,正如您已经注意到的,没有保存,性能是相同的! 让我们进入
np.save()
方法

Np.save方法
np.save
负责io流和调用方法。这对于C_连续阵列来说是非常快的。(快速存取存储器)

让我们验证一下这个假设:

np.squeeze( np.zeros((512,512,256))[roix[0]:roix[1]+1, roix[2]:roix[3]+1, roix[4]:roix[5]+1] ).flags.c_contiguous # returns True
因此,这可能解释了
roix
roiy
/
roiz
之间的差异

roiy
roiz
之间差异的潜在解释。数据传输减慢了程序的速度 在那之后,我只能做出假设,
roiz
似乎比
roiy
更零碎。哪个t
np.squeeze( np.zeros((512,512,256))[roiy[0]:roiy[1]+1, roiy[2]:roiy[3]+1, roiy[4]:roiy[5]+1] ).flags.c_contiguous # returns False
np.squeeze( np.zeros((512,512,256))[roiz[0]:roiz[1]+1, roiz[2]:roiz[3]+1, roiz[4]:roiz[5]+1] ).flags.c_contiguous # returns False