Python 按列求和V按行求和:为什么不';我看不出使用NumPy有什么不同吗?
我已经使用numpy(第20/57页)测试了[pytables]中演示的一个示例 据说,Python 按列求和V按行求和:为什么不';我看不出使用NumPy有什么不同吗?,python,performance,numpy,Python,Performance,Numpy,我已经使用numpy(第20/57页)测试了[pytables]中演示的一个示例 据说,a[:,1].sum()需要9.3毫秒,而a[1,:].sum()只需要72毫秒 我试图复制它,但没有成功。我量错了吗?或者2010年以来,努比的情况发生了变化 $ python2 -m timeit -n1000 --setup \ 'import numpy as np; a = np.random.randn(4000,4000);' 'a[:,1].sum()' 1000 loops, bes
a[:,1].sum()
需要9.3毫秒,而a[1,:].sum()
只需要72毫秒
我试图复制它,但没有成功。我量错了吗?或者2010年以来,努比的情况发生了变化
$ python2 -m timeit -n1000 --setup \
'import numpy as np; a = np.random.randn(4000,4000);' 'a[:,1].sum()'
1000 loops, best of 3: 16.5 usec per loop
$ python2 -m timeit -n1000 --setup \
'import numpy as np; a = np.random.randn(4000,4000);' 'a[1,:].sum()'
1000 loops, best of 3: 13.8 usec per loop
$ python2 --version
Python 2.7.7
$ python2 -c 'import numpy; print numpy.version.version'
1.8.1
虽然我可以衡量第二个版本的优点(因为numpy使用C风格的行排序,所以应该可以减少缓存未命中),但我看不到pytables贡献者所说的显著差异
此外,在使用第V列行求和时,似乎看不到更多的缓存未命中
编辑
- 到目前为止,我的理解是我用错了
模块。使用同一数组(或数组的行/列)重复运行几乎肯定会被缓存(我有一级数据缓存的timeit
,所以里面有一行非常合适:32KiB
)4000*4字节=15k<32k
- 使用@alim中的脚本进行单循环(
)和十次试验nloop=1
,并改变随机数组的大小(nrep=10
),我正在测量nxn
*n row/us col/us penalty col 1k 90 100 1 4k 100 210 2 10k* 110 350 3.5 20k* 120 1200 10
及更高版本不再适合L1d缓存n=10k
perf
显示,对于更快的行和,缓存未命中率大致相同(有时甚至更高)
Perf
数据:
nloop=2
和nrep=2
,因此我希望缓存中仍有一些数据。。。第二轮
行和n=10k
列和n=10k
EDIT2
我想我已经理解了一些方面,但我想这个问题还没有得到回答。目前,我认为这个求和示例根本没有揭示任何关于CPU缓存的信息。为了消除numpy/python的不确定性,我尝试使用
perf
在C中进行求和,结果如下。我看不出您尝试复制有任何错误,但请记住,这些幻灯片是从2010年开始的,从那以后numpy发生了很大的变化。基于这一点,我猜Francesc可能正在使用v1.5
使用此脚本对第v行列和进行基准测试:
#!python
import numpy as np
import timeit
print "numpy version == " + str(np.__version__)
setup = "import numpy as np; a = np.random.randn(4000, 4000)"
rsum = "a[1, :].sum()"
csum = "a[:, 1].sum()"
nloop = 1000
nrep = 3
print "row sum:\t%.3f us" % (
min(timeit.repeat(rsum, setup, repeat=nrep, number=nloop)) / nloop * 1E6)
print "column sum:\t%.3f us" % (
min(timeit.repeat(csum, setup, repeat=nrep, number=nloop)) / nloop * 1E6)
我检测到numpy v1.5的列和速度降低了约50%:
$ python sum_benchmark.py
numpy version == 1.5.0
row sum: 8.472 us
column sum: 12.759 us
与您正在使用的v1.8.1相比,速度降低了约30%:
$ python sum_benchmark.py
numpy version == 1.8.1
row sum: 12.108 us
column sum: 15.768 us
有趣的是,在最近的numpy版本中,这两种类型的缩减实际上都变得有点慢。我必须深入研究numpy的源代码
要准确理解为什么会这样
更新
- 为了记录在案,我在2.0GHz的四核i7-2630QM CPU上运行Ubuntu14.04(内核v3.13.0-30)。numpy的两个版本都是使用GCC-4.8.1进行pip安装和编译的
- 我意识到我最初的基准测试脚本并不是完全不言自明的——您需要将总时间除以循环数(1000)才能得到每次调用的时间
- 另外,因为这更可能代表执行时间的下限(在执行时间的上限上,由于后台进程等,您会得到可变性)
nloop
设置为1,将nrep
设置为一个合理的小数字(除非你真的喜欢看油漆干),比如10
nloop=1
,nreps=10
在4000x4000阵列上:
numpy version == 1.5.0
row sum: 47.922 us
column sum: 103.235 us
numpy version == 1.8.1
row sum: 66.996 us
column sum: 125.885 us
这有点像,但我仍然无法真正复制Francesc幻灯片所展示的巨大效果。也许这并不令人惊讶,但是-效果可能非常依赖于编译器、体系结构和/或内核。我使用的是Numpy 1.9.0.def-ff7d5f9,在执行您发布的两个测试行时,我看到了10倍的差异。如果您的机器和用于构建Numpy的编译器对加速的重要性与Numpy版本一样,我也不会感到惊讶 但在实践中,我认为这样减少一列或一行并不常见。我认为一个更好的测试是比较所有行的减少
a.sum(axis=0)
在所有列中减少
a.sum(axis=1)
对我来说,这两个操作在速度上只有很小的差异(跨列减少大约需要跨行减少95%的时间)
编辑:一般来说,我对比较微秒级的运算速度非常谨慎。在安装Numpy时,与它有良好的链接是非常重要的,因为对于大多数大型矩阵运算(如矩阵乘法),这是最重要的。在比较BLAS库时,您肯定希望使用密集型操作(如矩阵-点积)作为比较点,因为这是您花费大量时间的地方。我发现有时候,一个更差的BLAS库实际上比一个更好的BLAS库有一个稍微快一点的矢量点运算。然而,更糟糕的是,像矩阵点积和特征值分解这样的运算需要几十倍的时间,而这些比廉价的向量点更重要。我认为这些差异经常出现,因为您可以不用花太多心思就用C编写一个速度相当快的向量点,但是编写一个好的矩阵点需要大量的思考和优化,而且是更昂贵的操作,因此这是好的BLAS包的努力方向
Numpy也是如此:任何优化都将在较大的操作上进行,而不是在较小的操作上进行,因此不要被小操作之间的速度差异所困扰。此外,很难做到这一点
a.sum(axis=0)
a.sum(axis=1)
In [21]: np.__version__
Out[21]: '1.8.1'
In [22]: a = np.random.randn(4000, 4000)
In [23]: %timeit a[:, 1].sum()
100000 loops, best of 3: 12.4 µs per loop
In [24]: %timeit a[1, :].sum()
100000 loops, best of 3: 10.6 µs per loop
In [25]: a = np.random.randn(10000, 10000)
In [26]: %timeit a[:, 1].sum()
10000 loops, best of 3: 21.8 µs per loop
In [27]: %timeit a[1, :].sum()
100000 loops, best of 3: 15.8 µs per loop
In [28]: a = np.random.randn(10000, 10000)
In [29]: %timeit a[:, 1].sum()
10000 loops, best of 3: 64.4 µs per loop
In [30]: %timeit a[1, :].sum()
100000 loops, best of 3: 15.9 µs per loop
n first/us converged/us
1k 5 4
4k 19 12
10k 35 31
20k 70 61
30k 130 90
Run 0 taken 70 cycles. 0 ms 70 us
Run 1 taken 61 cycles. 0 ms 60 us # this is the minimum I've seen in all tests
Run 1 taken 61 cycles. 0 ms 61 us
<snip> (always 60/61 cycles)
n first/us converged/us
1k 5 4
4k 112 14
10k 228 32
20k 550 246
30k 1000 300
Run 0 taken 552 cycles. 0 ms 552 us
Run 1 taken 358 cycles. 0 ms 358 us
Run 2 taken 291 cycles. 0 ms 291 us
Run 3 taken 264 cycles. 0 ms 264 us
Run 4 taken 252 cycles. 0 ms 252 us
Run 5 taken 275 cycles. 0 ms 275 us
Run 6 taken 262 cycles. 0 ms 262 us
Run 7 taken 249 cycles. 0 ms 249 us
Run 8 taken 249 cycles. 0 ms 249 us
Run 9 taken 246 cycles. 0 ms 246 us
#include <stdio.h>
#include <stdlib.h> // see `man random`
#include <time.h> // man time.h, info clock
int
main (void)
{
// seed
srandom(62);
//printf ("test %g\n", (double)random()/(double)RAND_MAX);
const size_t SIZE = 20E3;
const size_t RUNS = 10;
double (*b)[SIZE];
printf ("Array size: %dx%d, each %d bytes. slice = %f KiB\n", SIZE, SIZE,
sizeof(double), ((double)SIZE)*sizeof(double)/1024);
b = malloc(sizeof *b * SIZE);
//double a[SIZE][SIZE]; // too large!
int i,j;
for (i = 0; i< SIZE; i++) {
for (j = 0; j < SIZE; j++) {
b[i][j] = (double)random()/(double)RAND_MAX;
}
}
double sum = 0;
int run = 0;
clock_t start, diff;
int usec;
for (run = 0; run < RUNS; run++) {
start = clock();
for (i = 0; i<SIZE; i++) {
// column wise (slower?)
sum += b[i][1];
// row wise (faster?)
//sum += b[1][i];
}
diff = clock() - start;
usec = ((double) diff*1e6) / CLOCKS_PER_SEC; // https://stackoverflow.com/a/459704/543411
printf("Run %d taken %d cycles. %d ms %d us\n",run, diff, usec/1000, usec%1000);
}
printf("Sum: %g\n", sum);
return 0;
}