A.9 性能建议

使用NumPy的代码的性能一般都很不错,因为数组运算一般都比纯Python循环快得多。下面大致列出了一些需要注意的事项:

  • 将Python循环和条件逻辑转换为数组运算和布尔数组运算。
  • 尽量使用广播。
  • 避免复制数据,尽量使用数组视图(即切片)。
  • 利用ufunc及其各种方法。

如果单用NumPy无论如何都达不到所需的性能指标,就可以考虑一下用C、Fortran或Cython(等下会稍微介绍一下)来编写代码。我自己在工作中经常会用到Cython(http://cython.org),因为它不用花费我太多精力就能得到C语言那样的性能。

连续内存的重要性

虽然这个话题有点超出本书的范围,但还是要提一下,因为在某些应用场景中,数组的内存布局可以对计算速度造成极大的影响。这是因为性能差别在一定程度上跟CPU的高速缓存(cache)体系有关。运算过程中访问连续内存块(例如,对以C顺序存储的数组的行求和)一般是最快的,因为内存子系统会将适当的内存块缓存到超高速的L1或L2CPU Cache中。此外,NumPy的C语言基础代码(某些)对连续存储的情况进行了优化处理,这样就能避免一些跨越式的内存访问。

一个数组的内存布局是连续的,就是说元素是以它们在数组中出现的顺序(即Fortran型(列优先)或C型(行优先))存储在内存中的。默认情况下,NumPy数组是以C型连续的方式创建的。列优先的数组(比如C型连续数组的转置)也被称为Fortran型连续。通过ndarray的flags属性即可查看这些信息:

  1. In [225]: arr_c = np.ones((1000, 1000), order='C')
  2. In [226]: arr_f = np.ones((1000, 1000), order='F')
  3. In [227]: arr_c.flags
  4. Out[227]:
  5. C_CONTIGUOUS : True
  6. F_CONTIGUOUS : False
  7. OWNDATA : True
  8. WRITEABLE : True
  9. ALIGNED : True
  10. UPDATEIFCOPY : False
  11. In [228]: arr_f.flags
  12. Out[228]:
  13. C_CONTIGUOUS : False
  14. F_CONTIGUOUS : True
  15. OWNDATA : True
  16. WRITEABLE : True
  17. ALIGNED : True
  18. UPDATEIFCOPY : False
  19. In [229]: arr_f.flags.f_contiguous
  20. Out[229]: True

在这个例子中,对两个数组的行进行求和计算,理论上说,arr_c会比arr_f快,因为arr_c的行在内存中是连续的。我们可以在IPython中用%timeit来确认一下:

  1. In [230]: %timeit arr_c.sum(1)
  2. 784 us +- 10.4 us per loop (mean +- std. dev. of 7 runs, 1000 loops each)
  3. In [231]: %timeit arr_f.sum(1)
  4. 934 us +- 29 us per loop (mean +- std. dev. of 7 runs, 1000 loops each)

如果想从NumPy中提升性能,这里就应该是下手的地方。如果数组的内存顺序不符合你的要求,使用copy并传入’C’或’F’即可解决该问题:

  1. In [232]: arr_f.copy('C').flags
  2. Out[232]:
  3. C_CONTIGUOUS : True
  4. F_CONTIGUOUS : False
  5. OWNDATA : True
  6. WRITEABLE : True
  7. ALIGNED : True
  8. UPDATEIFCOPY : False

注意,在构造数组的视图时,其结果不一定是连续的:

  1. In [233]: arr_c[:50].flags.contiguous
  2. Out[233]: True
  3. In [234]: arr_c[:, :50].flags
  4. Out[234]:
  5. C_CONTIGUOUS : False
  6. F_CONTIGUOUS : False
  7. OWNDATA : False
  8. WRITEABLE : True
  9. ALIGNED : True
  10. UPDATEIFCOPY : False