我终于在我的代码中发现了一个性能瓶颈,但对于原因是什么感到困惑.为了解决这个问题,我改变了所有的调用numpy.zeros_like来代替使用numpy.zeros.但为什么zeros_likesooooo慢得多?
例如(注意e-05在zeros电话):
>>> 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.ones并np.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已拨打malloc和calloc.
https://github.com/numpy/numpy/blob/master/numpy/core/src/multiarray/ctors.c - 来源zeros和empty.两个电话PyArray_NewFromDescr_int,但最终使用npy_alloc_cache_zero和另一个npy_alloc_cache.
npy_alloc_cache在alloc.c电话中alloc. npy_alloc_cache_zero电话npy_alloc_cache跟着一个memset.代码输入alloc.c与THREAD选项进一步混淆.
更多关于callocv的malloc+memset区别:
为什么malloc + memset比calloc慢?
但是通过缓存和垃圾收集,我想知道这种calloc/memset区别是否适用.
这个memory_profile包的简单测试支持声明zeros并empty"即时"分配内存,同时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)
pv.*_*pv. 16
现代操作系统虚拟地分配存储器,即,仅在首次使用时将存储器提供给进程.zeros从操作系统获取内存,以便操作系统在首次使用时将其归零.zeros_like另一方面,用自己的零填充分配的内存.这两种方式都需要大约相同的工作量 - 这只是zeros_like预先完成归零,而zeros最终会在运行中完成.
从技术上讲,在C的差别呼吁calloc对malloc+memset.