Python 按列求和V按行求和:为什么不';我看不出使用NumPy有什么不同吗?

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

我已经使用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, 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
    
    *
    n=10k
    及更高版本不再适合L1d缓存

我仍然不确定是否要找出原因,因为
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;
}