【问题标题】:Why is using a Cython list faster than using a Python list?为什么使用 Cython 列表比使用 Python 列表更快?
【发布时间】:2014-10-12 06:39:37
【问题描述】:

这是我的 Python 代码:

X = [[0] * 1000] * 100
start = time()
for x in xrange(100):
    for i in xrange(len(X)):
        for j in xrange(len(X[i])):
            X[i][j] += 1
print  time() - start

我的 Cython 代码是一样的:

X = [[0] * 1000] * 100
start = time()
for x in xrange(100):
    for i in xrange(len(X)):
        for j in xrange(len(X[i])):
            X[i][j] += 1
print  time() - start

输出:

  • Python 成本:2.86 秒
  • Cython 消耗:0.41 秒

还有其他更快的方法在 Python 或 Cython 中执行上述操作吗?

更新:有什么方法可以创建高度索引性能接近 C/C++ 中的数组 int X[][] 的二维数组 X?

目前我正在考虑使用 Python C API 来完成这项工作。

还有一件事,一个 numpy 数组做同样的事情,但比纯 Python 和 Cython 中的列表慢得多(70 秒)。

Python:

X = np.zeros((100,1000),dtype=np.int32)
start = time()
for x in xrange(100):
    for i in xrange(len(X)):
        for j in xrange(len(X[i])):
            X[i][j]+=1

如果对数值数组进行大量访问,哪种方法最好?

【问题讨论】:

  • 你能用numpy显示代码吗?
  • 上一个关于为什么它更快以及为速度付出代价的问题stackoverflow.com/questions/2697275/… 利息
  • @kroolik 添加了 numpy 数组代码
  • numpy 如果按预期使用会非常快,即通过编写 vectorized 代码。您的 numpy 示例可以简单地写为Z = np.zeros((100, 1000), np.int32); X += 1(或者更简单,写为np.ones((100, 1000), np.int32)),这应该非常快(我机器上的就地添加约85us)。如果您发布您正在尝试优化的实际功能,那么我们很有可能可以帮助您编写矢量化版本。
  • 我并不感到惊讶——实际上没有一个代码是矢量化的!不是数组容器本身更快(访问单个元素实际上比使用列表慢),而是它允许您一次将操作应用于多个数组元素,从而避免在 Python 中循环遍历元素。要充分利用 numpy,您确实必须停止将数组视为嵌套列表,并了解向量化操作的概念 (see here for a tutorial)。

标签: python performance list numpy cython


【解决方案1】:

要回答您标题中的问题,您的 Cython 代码胜过您的 Python 代码,因为尽管缺少 cdef 来声明变量,但正在为 for 循环生成 C 代码(除了大量额外的 C描述 Python 对象的代码)。要加快 Cython 代码的速度,请使用 cdef 声明整数 ijx,以便它们不再是 Python 整数:例如cdef int i。您也可以在 Cython 中 declare C-type arrays,这应该会进一步提高性能。

使用 NumPy 获得相同结果的快速方法:

X = np.zeros((100, 1000), dtype=np.int32)
X += 10000

如果你能提供帮助,你永远不应该在 NumPy 数组中使用 for 循环。它们在内存使用方面与列表完全不同。

【讨论】:

    【解决方案2】:

    在 Python 或 Cython 中执行上述相同操作的任何其他更快的方法?

    等效的、更快的代码是:

    X = [[100 * 100] * 1000] * 100
    

    在您的代码中,您正在创建一个由零组成的1000-long 列表,然后为该列表创建一个引用100-long 列表。现在,在 100-long 列表上迭代 100 次会导致每个位置增加 100 * 100 = 10000 次。

    len(set(map(id, X)))
    1
    

    如果您希望得到100 列表的列表:

    base = [100] * 1000
    X = [list(base) for _ in xrange(100)]
    len(set(map(id, X)))
    100
    

    请注意,对列表内对象的引用仍然被复制。

    【讨论】:

    • 谢谢kroolik,您的代码完全正确。没有把我的想法描述清楚是我的错。我想找到一种使用高性能二维数组的快速方法。就像 C 代码中的 int a[][]。
    • @lessisawesome,目的是什么?最通用的一个是您发布的那个 - 在 Cython 中的内部循环中包含您需要的代码的双循环。考虑到特定用例,可以进行更多优化。
    【解决方案3】:

    ajcr 的答案可能是最快和最简单的答案。您应该首先在 cython 代码中显式声明变量的数据类型。此外,我会为外部循环创建一个 prange 而不是简单的 range 迭代器。这将激活 OpenMP 多线程,这可能会进一步加速您的代码,但我真的怀疑这个解决方案是否会击败 numpy 实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-07
      • 2011-03-21
      • 1970-01-01
      • 2015-12-05
      • 2015-06-29
      • 2015-09-29
      • 2016-08-24
      • 2013-05-13
      相关资源
      最近更新 更多