【问题标题】:Why is numpy list access slower than vanilla python?为什么 numpy 列表访问比 vanilla python 慢?
【发布时间】:2016-05-03 09:58:08
【问题描述】:

我的印象是 numpy 对于列表操作会更快,但以下示例似乎表明并非如此:

import numpy as np
import time

def ver1():
    a = [i for i in range(40)]
    b = [0 for i in range(40)]
    for i in range(1000000):
        for j in range(40):
            b[j]=a[j]

def ver2():
    a = np.array([i for i in range(40)])
    b = np.array([0 for i in range(40)])
    for i in range(1000000):
        for j in range(40):
            b[j]=a[j]

t0 = time.time()
ver1()
t1 = time.time()
ver2()
t2 = time.time()

print(t1-t0)
print(t2-t1)

输出是:

4.872278928756714
9.120521068572998

(我在 i7 920 上的 Windows 7 中运行 64 位 Python 3.4.3)

我知道这不是复制列表的最快方法,但我正在尝试找出我是否错误地使用了 numpy。还是说 numpy 对于这种操作比较慢,只在更复杂的操作中效率更高?

编辑:

我还尝试了以下方法,它只是通过 b[:] = a 直接复制,numpy 仍然慢两倍:

import numpy as np
import time

def ver6():
    a = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    b = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    for i in range(1000000):
        b[:] = a

def ver7():
    a = np.array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
    b = np.array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
    for i in range(1000000):
        b[:] = a

t0 = time.time()
ver6()
t1 = time.time()
ver7()
t2 = time.time()

print(t1-t0)
print(t2-t1)

输出是:

0.36202096939086914
0.6750380992889404

【问题讨论】:

  • NumPy 新手的经验法则:如果您的代码中包含 for 一词,那么您将无法获得 NumPy 的好处。
  • 构造 numpy 数组需要一些时间。然而,在构造它们之后,进一步的操作比使用普通 Python 列表要快得多。由于您在每次循环迭代中都构造了两个新的 numpy 数组,因此它比使用 Python 列表要慢得多才有意义。
  • @pzp 不,numpy 数组只创建一次。
  • @pzp 正是出于这个原因,我更改了代码以在函数之外构造数组,将其简化为单个函数并在没有该因素的情况下对其进行计时。还是一样。
  • @roganjosh 你确定吗?我只是自己计时,不包括数组构造,并且 user2357112 正确使用了 numpy 和 numpy killed vanilla——它甚至没有接近。此外,这是一个构建得很糟糕的测试......结果正在被缓存。

标签: python performance numpy


【解决方案1】:

你用错了 NumPy。 NumPy 的效率依赖于在 C 级循环中做尽可能多的工作,而不是解释代码。当你这样做时

for j in range(40):
    b[j]=a[j]

这是一个解释循环,具有所有固有的解释器开销等等,因为 NumPy 的索引逻辑比列表索引复杂得多,并且 NumPy 需要在每次元素检索时创建一个新的元素包装器对象。当您编写这样的代码时,您不会获得 NumPy 的任何好处。

您需要以这样一种方式编写代码,使工作发生在 C 中:

b[:] = a

这也将提高列表操作的效率,但对 NumPy 来说更为重要。

【讨论】:

  • @L3viathan:那没用;事实上,这是完全错误的。真的,数组应该是np.arange(40)numpy.zeros([40])
  • 嗨,我用 b[:] = a 试过了,香草 python 的速度仍然是 numpy 的两倍以上。
  • @CaptainCodeman:这是三个因素的结合:输入相当小,涉及的分配很少,Python 列表也将工作推到 C 中。如果您尝试使用更大的数组,或者尝试数学运算(例如,元素加法),NumPy 数组会更快。
  • @CaptainCodeman:取决于你做多少数学,以及你在做数学时如何利用 NumPy 的功能。即使对于这种大小的数组,NumPy 也比 Python 内置的数学数据类型快得多。
  • @roganjosh:值得注意的是,对于实际的数学运算,NumPy 以更短的数组长度开始获胜。例如,a+b with arrays beats [x+y for x, y in zip(a, b)] for lists at a length of about 10numpy.log(a) beats [math.log(x) for x in a] at a length of about 7
【解决方案2】:

您看到的大部分内容是从 C 本机类型创建 Python 对象。

Python 列表的核心是PyObject 指针数组。当ab 都是Python 列表时,b[i] = a[i] 将意味着:

  • 减少b[i]指向的对象的引用计数,
  • 增加a[i]指向的对象的引用计数,并且
  • a[i]中存储的地址复制到b[i]中。

但如果 ab 是 NumPy 数组,事情会更精细一些,同样的 b[i] = a[i] 则需要:

  • 从存储在 a[i] 的本机 C 整数类型创建 Python 整数对象,请参阅 this
  • 将 Python 整数对象转换为原生 C 整数类型,并将其值存储在 b[i] 中,参见 here,以及
  • 减少临时 Python 整数对象的引用计数。

所以区别主要在于创建和处置那个中间 Python 对象,而列表不需要这样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-17
    • 1970-01-01
    • 2017-11-09
    • 2015-05-30
    • 2020-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多