纯Python比Numpy快?如何让这段Numpy代码更快?

4 投票
2 回答
1119 浏览
提问于 2025-04-17 16:46

我需要从一个特定的面/顶点列表中计算最小值、最大值和平均值。我尝试用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倍!

2 个回答

2

正如其他人已经提到的,你的问题在于将列表转换为数组。使用合适的 numpy 函数来处理这个问题,你的代码会运行得更快。我对你程序的主要部分做了一些修改:

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 版本。

10

你发现你的 FnumpyFpython 慢,原因是 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.

撰写回答