我正在为一个游戏编写一段代码,该代码使用游戏中的坐标位置计算屏幕上所有对象之间的距离。最初我打算使用基本的 Python 和列表来执行此操作,但由于需要计算的距离数量会随着对象数量的增加呈指数级增长,我认为使用numpy
可能会更快。
我对numpy
不是很熟悉,我一直在用它试验基本的代码。 我写了一些代码来计算同一个函数在numpy
和常规 Python 中完成计算所需的时间,numpy
似乎总是比常规 Python 花费更多的时间。
功能非常简单。它从 1.1 开始,然后递增 200,000 倍,在最后一个值上加 0.1,然后找到新值的平方根。 这不是我将在游戏代码中实际要做的事情,这将涉及从位置坐标中查找总距离向量;这只是我拼凑的一个快速测试。 我已经在这里读到数组的初始化在 NumPy 中需要更多时间,所以我将numpy
和 python 数组的初始化都移到了它们的函数之外,但 Python 仍然比numpy
快。
这是一段代码:
#!/usr/bin/python3
import numpy
from timeit import timeit
#from time import process_time as timer
import math
thing = numpy.array([1.1,0.0], dtype='float')
thing2 = [1.1,0.0]
def NPFunc():
for x in range(1,200000):
thing[0] += 0.1
thing[1] = numpy.sqrt(thing[0])
print(thing)
return None
def PyFunc():
for x in range(1,200000):
thing2[0] += 0.1
thing2[1] = math.sqrt(thing2[0])
print(thing2)
return None
print(timeit(NPFunc, number=1))
print(timeit(PyFunc, number=1))
它给出了这个结果,这表明普通的Python快了3倍:
[ 20000.99999999 141.42489173]
0.2917748889885843
[20000.99999998944, 141.42489172698504]
0.10341173503547907
我做错了什么吗,这个计算是不是太简单了,对numpy
来说不是一个很好的测试?
我做错了什么吗,这个计算是否如此简单,以至于对 NumPy 来说不是一个很好的测试?
这并不是说计算很简单,而是你没有利用NumPy的任何优势。
NumPy的主要好处是矢量化:你可以一次性将一个操作应用于数组的每个元素,并且任何需要的循环都发生在NumPy内部一些严格优化的C(或Fortran或C++或其他)循环中,而不是在缓慢的通用Python迭代中。
但是您只访问单个值,因此在 C 中无需执行循环。
最重要的是,由于数组中的值存储为"本机"值,因此NumPy函数不需要将它们拆箱,将原始Cdouble
从Pythonfloat
中提取出来,然后在新的Pythonfloat
中重新装箱,就像任何Python数学函数一样。
但你也没有这样做。事实上,你正在将这项工作加倍:你将数组的值作为float
(装箱),然后将其传递给函数(函数必须将其拆箱,然后重新装箱以返回结果),然后将其存储回数组中(再次将其拆箱)。
同时,由于np.sqrt
被设计用于处理数组,因此它必须首先检查您传递它的内容的类型,并决定它是否需要遍历数组或取消装箱并重新装箱单个值或其他任何值,而math.sqrt
只接受单个值。当您在包含 200000 个元素的数组上调用np.sqrt
时,该类型开关的额外成本可以忽略不计,但是当您每次都通过内部循环执行此操作时,情况就不同了。
所以,这不是一个不公平的测试。
您已经证明,使用 NumPy 一次提取一个值,一次一个地操作它们,然后一次将它们存储回数组中比不使用 NumPy 要慢。
但是,如果您将其与实际利用 NumPy 进行比较(例如,通过创建一个包含 200000 个浮点数的数组,然后在该数组上调用np.sqrt
,而不是循环访问它并在每个数组上调用math.sqrt
——您将证明按照预期的方式使用 NumPy 比不使用它更快。
你比较错了
a_list = np.arange(0,20000,0.1)
timeit(lambda:np.sqrt(a_list),number=1)