为什么numpy.zeros和numpy.zeros_like之间的性能差异?

Dam*_*ria 20 python numpy

我终于在我的代码中发现了一个性能瓶颈,但对于原因是什么感到困惑.为了解决这个问题,我改变了所有的调用numpy.zeros_like来代替使用numpy.zeros.但为什么zeros_likesooooo慢得多?

例如(注意e-05zeros电话):

>>> timeit.timeit('np.zeros((12488, 7588, 3), np.uint8)', 'import numpy as np', number = 10)
5.2928924560546875e-05
>>> timeit.timeit('np.zeros_like(x)', 'import numpy as np; x = np.zeros((12488, 7588, 3), np.uint8)', number = 10)
1.4402990341186523
Run Code Online (Sandbox Code Playgroud)

但奇怪的是,写入创建的数组zeros明显比使用以下创建的数组慢zeros_like:

>>> timeit.timeit('x[100:-100, 100:-100] = 1', 'import numpy as np; x = np.zeros((12488, 7588, 3), np.uint8)', number = 10)
0.4310588836669922
>>> timeit.timeit('x[100:-100, 100:-100] = 1', 'import numpy as np; x = np.zeros_like(np.zeros((12488, 7588, 3), np.uint8))', number = 10)
0.33325695991516113
Run Code Online (Sandbox Code Playgroud)

我的猜测是zeros使用一些CPU技巧而不是实际写入内存来分配它.这是在写入时动态完成的.但这仍然无法解释数组创建时间的巨大差异.

我使用当前的numpy版本运行Mac OS X Yosemite:

>>> numpy.__version__
'1.9.1'
Run Code Online (Sandbox Code Playgroud)

hpa*_*ulj 16

我在Ipython中的时间是(具有更简单的timeit接口):

In [57]: timeit np.zeros_like(x)
1 loops, best of 3: 420 ms per loop

In [58]: timeit np.zeros((12488, 7588, 3), np.uint8)
100000 loops, best of 3: 15.1 µs per loop
Run Code Online (Sandbox Code Playgroud)

当我用IPython(np.zeros_like??)查看代码时,我看到:

res = empty_like(a, dtype=dtype, order=order, subok=subok)
multiarray.copyto(res, 0, casting='unsafe')
Run Code Online (Sandbox Code Playgroud)

np.zeros黑盒子 - 纯编译代码.

时间安排empty是:

In [63]: timeit np.empty_like(x)
100000 loops, best of 3: 13.6 µs per loop

In [64]: timeit np.empty((12488, 7588, 3), np.uint8)
100000 loops, best of 3: 14.9 µs per loop
Run Code Online (Sandbox Code Playgroud)

因此额外的时间zeros_like就在那里copy.

在我的测试中,赋值时间(x[]=1)的差异可以忽略不计.

我的猜测是zeros,ones,empty都是早期编译作品. empty_like添加是为了方便,只是从输入中绘制形状和类型信息. zeros_like编写时更注重简单的编程维护(重用empty_like)而不是速度.

np.onesnp.full使用np.empty ... copyto序列,并显示类似的时间.


https://github.com/numpy/numpy/blob/master/numpy/core/src/multiarray/array_assign_scalar.c 似乎是将标量(例如0)复制到数组的文件.我没有看到使用memset.

https://github.com/numpy/numpy/blob/master/numpy/core/src/multiarray/alloc.c已拨打malloccalloc.

https://github.com/numpy/numpy/blob/master/numpy/core/src/multiarray/ctors.c - 来源zerosempty.两个电话PyArray_NewFromDescr_int,但最终使用npy_alloc_cache_zero和另一个npy_alloc_cache.

npy_alloc_cachealloc.c电话中alloc. npy_alloc_cache_zero电话npy_alloc_cache跟着一个memset.代码输入alloc.c与THREAD选项进一步混淆.

更多关于callocv的malloc+memset区别: 为什么malloc + memset比calloc慢?

但是通过缓存和垃圾收集,我想知道这种calloc/memset区别是否适用.


这个memory_profile包的简单测试支持声明zerosempty"即时"分配内存,同时zeros_like预先分配所有内容:

N = (1000, 1000) 
M = (slice(None, 500, None), slice(500, None, None))

Line #    Mem usage    Increment   Line Contents
================================================
     2   17.699 MiB    0.000 MiB   @profile
     3                             def test1(N, M):
     4   17.699 MiB    0.000 MiB       print(N, M)
     5   17.699 MiB    0.000 MiB       x = np.zeros(N)   # no memory jump
     6   17.699 MiB    0.000 MiB       y = np.empty(N)
     7   25.230 MiB    7.531 MiB       z = np.zeros_like(x) # initial jump
     8   29.098 MiB    3.867 MiB       x[M] = 1     # jump on usage
     9   32.965 MiB    3.867 MiB       y[M] = 1
    10   32.965 MiB    0.000 MiB       z[M] = 1
    11   32.965 MiB    0.000 MiB       return x,y,z
Run Code Online (Sandbox Code Playgroud)

  • 以上猜测不是原因.实际差异在于是将归零内存留给操作系统的VM子系统,还是由进程本身完成. (3认同)

pv.*_*pv. 16

现代操作系统虚拟地分配存储器,即,仅在首次使用时将存储器提供给进程.zeros从操作系统获取内存,以便操作系统在首次使用时将其归零.zeros_like另一方面,用自己的零填充分配的内存.这两种方式都需要大约相同的工作量 - 这只是zeros_like预先完成归零,而zeros最终会在运行中完成.

从技术上讲,在C的差别呼吁callocmalloc+memset.