我目前正在使用GDAL的Python绑定来处理相当大的光栅数据集(大于4gb)。因为一次将它们加载到内存中对我来说不是可行的解决方案,所以我把它们读入更小的块中,然后一块一块地进行计算。为了避免每次读块都重新分配,我使用buf_obj参数(here)将值读入预先分配的NumPy数组中。有一次我要计算整个光栅的平均值和标准差。当然,我使用np.std来进行计算。然而,通过分析程序的内存消耗,我意识到每次调用np.std时,都会分配和释放内存。在

演示此行为的最小工作示例:In [1] import numpy as np

In [2] a = np.random.rand(20e6) # Approx. 150 MiB of memory

In [3] %memit np.mean(a)

peak memory: 187.30 MiB, increment: 0.48 MiB

In [4] %memit np.std(a)

peak memory: 340.24 MiB, increment: 152.91 MiB

在GitHub上NumPy的源代码树中搜索发现,np.std函数在内部从_methods.py(here)调用{}函数。在某一点,_var计算与平均值的偏差并将其相加。因此,将创建输入数组的临时副本。该函数基本上计算标准差如下:

^{pr2}$

虽然这种方法对于较小的输入数组是可以的,但是对于较大的输入数组绝对没有办法。因为我使用的是之前提到的更小的内存块,从我的程序内存的角度来看,这个额外的拷贝并不是一个破坏游戏的问题。然而,让我恼火的是,对于每个块,在读取下一个块之前,会生成并释放一个新的分配。在

NumPy或SciPy中是否还有其他函数利用一种具有恒定内存消耗的方法,如Welford算法(Wikipedia)来一次性计算平均值和标准差?在

另一种方法是实现一个自定义版本的_var函数,并为预先分配的缓冲区(如NumPy ufuncs)提供可选的out参数。使用这种方法,不会消除额外的拷贝,但至少内存消耗是恒定的,并且每个块中分配的运行时都被保存了。在

编辑:测试了kezzos建议的Welford算法的Cython实现。在

Cython实现(由kezzos修改):cimport cython

cimport numpy as np

from libc.math cimport sqrt

@cython.boundscheck(False)

def iterative_approach(np.ndarray[np.float32_t, ndim=1] a):

cdef long n = 0

cdef float mean = 0

cdef float M2 = 0

cdef long i

cdef float delta

cdef float a_min = 10000000 # Must be set to Inf and -Inf for real cases

cdef float a_max = -10000000

for i in range(len(a)):

n += 1

delta = a[i] - mean

mean += delta / n

M2 += delta * (a[i] - mean)

if a[i] < a_min:

a_min = a[i]

if a[i] > a_max:

a_max = a[i]

return a_min, a_max, mean, sqrt(M2 / (n - 1))

NumPy实现(mean和std可以在一个函数中计算):def vector_approach(a):

return np.min(a), np.max(a), np.mean(a), np.std(a, ddof=1)

使用随机数据集的测试结果(以毫秒为单位的时间,最佳值为25):----------------------------------

| Size | Iterative | Vector |

----------------------------------

| 1e2 | 0.00529 | 0.17149 |

| 1e3 | 0.02027 | 0.16856 |

| 1e4 | 0.17850 | 0.23069 |

| 1e5 | 1.93980 | 0.77727 |

| 1e6 | 18.78207 | 8.83245 |

| 1e7 | 180.04069 | 101.14722 |

| 1e8 | 1789.60228 | 1086.66737 |

----------------------------------

对于较小的数据集,使用Cython的迭代方法似乎更快;对于包含10000+个元素的较大数据集,使用NumPy向量(可能是SIMD加速)方法更快。所有测试都是用python2.7.9和numpyversion1.9.2进行的。在

请注意,在实际情况下,toupper函数将用于计算光栅的单个块的统计信息。所有块的标准偏差和平均值将与Wikipedia(here)中建议的方法相结合。它的优点是不需要对光栅的所有元素进行求和,从而避免了浮点溢出问题(至少在某种程度上)。在