纯Python比Numpy更快吗?我能让这个Numpy代码运行得更快吗?

4

我需要计算特定面/顶点列表中的最小值、最大值和平均值。我尝试使用Numpy优化这个计算,但没有成功。

以下是我的测试用例:

#!/usr/bin/python
# -*- coding: iso-8859-15 -*-
'''
Module Started 22 févr. 2013
@note: test case comparaison numpy vs python
@author: Python4D/damien
'''

import numpy as np
import time


def Fnumpy(vertices):
  np_vertices=np.array(vertices)
  _x=np_vertices[:,:,0]
  _y=np_vertices[:,:,1]
  _z=np_vertices[:,:,2]
  _min=[np.min(_x),np.min(_y),np.min(_z)]
  _max=[np.max(_x),np.max(_y),np.max(_z)]
  _mean=[np.mean(_x),np.mean(_y),np.mean(_z)]
  return _mean,_max,_min

def Fpython(vertices):
  list_x=[item[0] for sublist in vertices for item in sublist]
  list_y=[item[1] for sublist in vertices for item in sublist]
  list_z=[item[2] for sublist in vertices for item in sublist]
  taille=len(list_x)
  _mean=[sum(list_x)/taille,sum(list_y)/taille,sum(list_z)/taille]
  _max=[max(list_x),max(list_y),max(list_z)]
  _min=[min(list_x),min(list_y),min(list_z)]    
  return _mean,_max,_min

if __name__=="__main__":
  vertices=[[[1.1,2.2,3.3,4.4]]*4]*1000000
  _t=time.clock()
  print ">>NUMPY >>{} for {}s.".format(Fnumpy(vertices),time.clock()-_t)
  _t=time.clock()
  print ">>PYTHON>>{} for {}s.".format(Fpython(vertices),time.clock()-_t)

结果如下所示:

结果为:

Numpy:

([1.1000000000452519, 2.2000000000905038, 3.3000000001880174], [1.1000000000000001, 2.2000000000000002, 3.2999999999999998], [1.1000000000000001, 2.2000000000000002, 3.2999999999999998]),耗时27.327068618秒。

Python:

([1.100000000045252, 2.200000000090504, 3.3000000001880174], [1.1, 2.2, 3.3], [1.1, 2.2, 3.3]),耗时1.81366938593秒。

纯Python比Numpy快15倍!


5
这行代码很慢:np_vertices=np.array(vertices)。你实际上并没有计时最小值和最大值函数,而是计时了整理嵌套引用所需的时间。 - YXD
你应该编辑你的问题,明确表达我认为你隐含的问题:“我能让这个numpy代码更快吗?”以避免被关闭。 - tacaswell
仅使用numpy构造(包括构建“vertices”),您可以显著加快代码的速度。 - Bálint Aradi
2个回答

10
您的 Fnumpy 运行速度较慢是因为它包含了一个额外的步骤,而这个步骤 Fpython 并没有执行:即在内存中创建一个 numpy 数组。如果将 np_verticies=np.array(verticies) 这行代码移到 Fnumpy 的计时部分之外,您的结果将会有很大不同。
>>NUMPY >>([1.1000000000452519, 2.2000000000905038, 3.3000000001880174], [1.1000000000000001, 2.2000000000000002, 3.2999999999999998], [1.1000000000000001, 2.2000000000000002, 3.2999999999999998]) for 0.500802s.
>>PYTHON>>([1.100000000045252, 2.200000000090504, 3.3000000001880174], [1.1, 2.2, 3.3], [1.1, 2.2, 3.3]) for 2.182239s.

您可以在创建numpy数组时提供数据类型提示,从而显著加快分配步骤。如果您告诉Numpy您有一个浮点数数组,即使您在计时循环中保留np.array()调用,它也会击败纯Python版本。
如果我将np_vertices=np.array(vertices)更改为np_vertices=np.array(vertices, dtype=np.float_)并将其保留在Fnumpy中,则Fnumpy版本将击败Fpython,即使它必须做更多的工作。
>>NUMPY >>([1.1000000000452519, 2.2000000000905038, 3.3000000001880174], [1.1000000000000001, 2.2000000000000002, 3.2999999999999998], [1.1000000000000001, 2.2000000000000002, 3.2999999999999998]) for 1.586066s.
>>PYTHON>>([1.100000000045252, 2.200000000090504, 3.3000000001880174], [1.1, 2.2, 3.3], [1.1, 2.2, 3.3]) for 2.196787s.

我尝试了np_vertices=np.array(vertices, dtype=np.float_)或np_vertices=np.array(vertices, dtype=np.half),但没有改善...
NUMPY>>([inf,inf,inf],[1.0996,2.1992,3.3008],[1.0996,2.1992,3.3008])需要27.5570968929秒。
PYTHON>>([1.100000000045252,2.200000000090504,3.3000000001880174],[1.1,2.2,3.3],[1.1,2.2,3.3])需要1.80307082548秒。
- baco
你确定吗?因为从我的结果看,我看到了巨大的改进。使用numpy 1.5.1和Python 2.7.1,如果有关系的话。 - Francis Avila
然而,更重要的一点是,您的numpy数组应该尽可能地创建/分配一次并重复使用,而不是在计算函数内部重新创建。内存分配也需要很长时间,并且应该考虑在任何算法中 - 它不仅限制程序速度的是计算。 - Francis Avila

2

正如其他人所指出的,你的问题在于将列表转换为数组。通过使用适当的 numpy 函数,你可以击败 Python。我修改了程序的主要部分:

if __name__=="__main__":
  _t = time.clock()
  vertices_np = np.resize(np.array([ 1.1, 2.2, 3.3, 4.4 ], dtype=np.float64), 
                          (1000000, 4, 4))
  print "Creating numpy vertices: {}".format(time.clock() - _t)
  _t = time.clock()
  vertices=[[[1.1,2.2,3.3,4.4]]*4]*1000000
  print "Creating python vertices: {}".format(time.clock() - _t)
  _t=time.clock()
  print ">>NUMPY >>{} for {}s.".format(Fnumpy(vertices_np),time.clock()-_t)
  _t=time.clock()
  print ">>PYTHON>>{} for {}s.".format(Fpython(vertices),time.clock()-_t)

在我的电脑上运行已修改的主要部分代码,结果如下:
Creating numpy vertices: 0.6
Creating python vertices: 0.01
>>NUMPY >>([1.1000000000452519, 2.2000000000905038, 3.3000000001880174], 
[1.1000000000000001, 2.2000000000000002, 3.2999999999999998], [1.1000000000000001, 
2.2000000000000002, 3.2999999999999998]) for 0.5s.
>>PYTHON>>([1.100000000045252, 2.200000000090504, 3.3000000001880174], [1.1, 2.2, 3.3], 
[1.1, 2.2, 3.3]) for 1.91s.

虽然使用Numpy工具创建数组仍然比使用Python的列表乘法运算符创建嵌套列表略长(0.6秒对比0.01秒),但是你可以在代码的运行时间关键部分获得约4倍的速度提升。如果我替换以下这一行:

np_vertices=np.array(vertices)

使用

np_vertices = np.asarray(vertices)

为了避免复制大数组,Numpy函数在我的机器上的运行时间甚至下降到0.37秒,比纯Python版本快5倍以上。 如果您在实际代码中提前知道顶点数,可以通过使用np.empty()预先分配相应的数组,然后用适当的数据填充它,并将其传递给您的函数的Numpy版本。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接