我的Python程序太慢了。所以,我分析它,发现大部分时间花在一个计算两点之间的距离的函数中(一个点是3个Python浮点数的列表):
def get_dist(pt0, pt1):
val = 0
for i in range(3):
val += (pt0[i] - pt1[i]) ** 2
val = math.sqrt(val)
return val
为了分析为什么这个函数如此慢,我编写了两个测试程序:一个用Python编写,一个用C ++编写类似的计算。他们计算了100万对点之间的距离。 (Python和C ++中的测试代码如下所示。)
Python计算需要2秒,而C ++需要0.02秒。 100倍的差异!
为什么Python代码比这些简单的数学计算的C ++代码慢得多?我如何加速以匹配C ++性能?
用于测试的Python代码:
import math, random, time
num = 1000000
# Generate random points and numbers
pt_list = []
rand_list = []
for i in range(num):
pt = []
for j in range(3):
pt.append(random.random())
pt_list.append(pt)
rand_list.append(random.randint(0, num - 1))
# Compute
beg_time = time.clock()
dist = 0
for i in range(num):
pt0 = pt_list[i]
ri = rand_list[i]
pt1 = pt_list[ri]
val = 0
for j in range(3):
val += (pt0[j] - pt1[j]) ** 2
val = math.sqrt(val)
dist += val
end_time = time.clock()
elap_time = (end_time - beg_time)
print elap_time
print dist
用于测试的C ++代码:
#include <cstdlib>
#include <iostream>
#include <ctime>
#include <cmath>
struct Point
{
double v[3];
};
int num = 1000000;
int main()
{
// Allocate memory
Point** pt_list = new Point*[num];
int* rand_list = new int[num];
// Generate random points and numbers
for ( int i = 0; i < num; ++i )
{
Point* pt = new Point;
for ( int j = 0; j < 3; ++j )
{
const double r = (double) rand() / (double) RAND_MAX;
pt->v[j] = r;
}
pt_list[i] = pt;
rand_list[i] = rand() % num;
}
// Compute
clock_t beg_time = clock();
double dist = 0;
for ( int i = 0; i < num; ++i )
{
const Point* pt0 = pt_list[i];
int r = rand_list[i];
const Point* pt1 = pt_list[r];
double val = 0;
for ( int j = 0; j < 3; ++j )
{
const double d = pt0->v[j] - pt1->v[j];
val += ( d * d );
}
val = sqrt(val);
dist += val;
}
clock_t end_time = clock();
double sec_time = (end_time - beg_time) / (double) CLOCKS_PER_SEC;
std::cout << sec_time << std::endl;
std::cout << dist << std::endl;
return 0;
}
答案 0 :(得分:6)
一系列优化:
import math, random, time
num = 1000000
# Generate random points and numbers
# Change #1: Sometimes it's good not to have too much randomness.
# This is one of those cases.
# Changing the code shouldn't change the results.
# Using a fixed seed ensures that the changes are valid.
# The final 'print dist' should yield the same result regardless of optimizations.
# Note: There's nothing magical about this seed.
# I randomly picked a hash tag from a git log.
random.seed (0x7126434a2ea2a259e9f4196cbb343b1e6d4c2fc8)
pt_list = []
rand_list = []
for i in range(num):
pt = []
for j in range(3):
pt.append(random.random())
pt_list.append(pt)
# Change #2: rand_list is computed in a separate loop.
# This ensures that upcoming optimizations will get the same results as
# this unoptimized version.
for i in range(num):
rand_list.append(random.randint(0, num - 1))
# Compute
beg_time = time.clock()
dist = 0
for i in range(num):
pt0 = pt_list[i]
ri = rand_list[i]
pt1 = pt_list[ri]
val = 0
for j in range(3):
val += (pt0[j] - pt1[j]) ** 2
val = math.sqrt(val)
dist += val
end_time = time.clock()
elap_time = (end_time - beg_time)
print elap_time
print dist
第一个优化(未显示)是在函数中嵌入除import
之外的所有代码。这个简单的更改使我的计算机的性能提升了36%。
**
运算符。你不在你的C代码中使用pow(d,2)
,因为每个人都知道这在C中是不理想的。它在python中也是次优的。 Python的**
很聪明;它将x**2
评估为x*x
。但是,聪明需要时间。您知道自己需要d*d
,因此请使用它。这是优化的计算循环:
for i in range(num):
pt0 = pt_list[i]
ri = rand_list[i]
pt1 = pt_list[ri]
val = 0
for j in range(3):
d = pt0[j] - pt1[j]
val += d*d
val = math.sqrt(val)
dist += val
你的Python代码看起来很像你的C代码。你没有利用这种语言。
import math, random, time, itertools
def main (num=1000000) :
# This small optimization speeds things up by a couple percent.
sqrt = math.sqrt
# Generate random points and numbers
random.seed (0x7126434a2ea2a259e9f4196cbb343b1e6d4c2fc8)
def random_point () :
return [random.random(), random.random(), random.random()]
def random_index () :
return random.randint(0, num-1)
# Big optimization:
# Don't generate the lists of points.
# Instead use list comprehensions that create iterators.
# It's best to avoid creating lists of millions of entities when you don't
# need those lists. You don't need the lists; you just need the iterators.
pt_list = [random_point() for i in xrange(num)]
rand_pts = [pt_list[random_index()] for i in xrange(num)]
# Compute
beg_time = time.clock()
dist = 0
# Don't loop over a range. That's too C-like.
# Instead loop over some iterable, preferably one that doesn't create the
# collection over which the iteration is to occur.
# This is particularly important when the collection is large.
for (pt0, pt1) in itertools.izip (pt_list, rand_pts) :
# Small optimization: inner loop inlined,
# intermediate variable 'val' eliminated.
d0 = pt0[0]-pt1[0]
d1 = pt0[1]-pt1[1]
d2 = pt0[2]-pt1[2]
dist += sqrt(d0*d0 + d1*d1 + d2*d2)
end_time = time.clock()
elap_time = (end_time - beg_time)
print elap_time
print dist
以下大约是代码定时部分中原始版本时间的1/40。不如C快,但接近。
注意注释掉“Mondo slow”计算。这大约是原始版本的十倍。使用numpy需要管理费用。与我的非numpy优化#3相比,后续代码中的设置需要相当长的时间。
结论:使用numpy时需要注意,设置成本可能很高。
import numpy, random, time
def main (num=1000000) :
# Generate random points and numbers
random.seed (0x7126434a2ea2a259e9f4196cbb343b1e6d4c2fc8)
def random_point () :
return [random.random(), random.random(), random.random()]
def random_index () :
return random.randint(0, num-1)
pt_list = numpy.array([random_point() for i in xrange(num)])
rand_pts = pt_list[[random_index() for i in xrange(num)],:]
# Compute
beg_time = time.clock()
# Mondo slow.
# dist = numpy.sum (
# numpy.apply_along_axis (
# numpy.linalg.norm, 1, pt_list - rand_pts))
# Mondo fast.
dist = numpy.sum ((numpy.sum ((pt_list-rand_pts)**2, axis=1))**0.5)
end_time = time.clock()
elap_time = (end_time - beg_time)
print elap_time
print dist
答案 1 :(得分:4)
一些一般提示:
将所有代码移动到main()函数中并使用普通的
if __name__ == "__main__":
main()
构造。由于可变范围,它极大地提高了速度。 有关原因的解释,请参阅Why does Python code run faster in a function?。
不要使用range()
,因为它会立即生成完整的范围,这对于大数字来说很慢;而是使用使用生成器的xrange()
。
答案 2 :(得分:2)
您不能期望在Python中匹配C ++性能,但是您可以稍微调整Python代码以使其更快:
def get_dist(pt0, pt1):
val = 0
for i in range(3):
val += (pt0[i] - pt1[i]) ** 2
val = math.sqrt(val)
return val
此代码的for
循环版本与您的C ++ for
循环完全不同。 Python版本创建一个列表然后迭代它,而C ++版本只是增加一个变量。如果你想加速Python版本,最好的方法就是明确地写出它来节省Python for
循环的开销。
def get_dist(pt0, pt1, sqrt=math.sqrt): # cache function at definition time
return sqrt((pt0[0] - pt1[0]) ** 2 + (pt0[1] - pt1[1]) ** 2 + (pt0[2] - pt1[2]) ** 2)
对于该特定功能而言,这可能与您可以获得的速度一样快(不使用numpy
),您也可以在主代码中改进其他内容。
答案 3 :(得分:2)
Python不是一种快速语言,它不会产生计算机代码,它在python虚拟机中运行。 &#34;一切&#34;是对象,所以你不像C中那样有静态类型。只有这样才能减慢它的速度。 - 无论如何,这不是我的区域所以我不会说太多。
您应该考虑使用PyPy,Cython,甚至可以在C语言中编写python扩展名。
我在PyPy中运行代码,使用的时间是250毫秒&lt; - 你正在寻找什么? 我为Cython编写了一个快速测试,并设法将其降低到500毫秒..
所以最好的选择是在速度非常重要时使用PyPy或Cython。
答案 4 :(得分:0)
这个页面变得非常混乱,大部分答案实际上都在评论中,所以这里是对可能的优化的快速概述:
Jamlak’s answer:优化你的python代码:
def get_dist(pt0, pt1, sqrt=math.sqrt): # cache function at definition time
return sqrt((pt0[0] - pt1[0]) ** 2 + (pt0[1] - pt1[1]) ** 2 + (pt0[2] - pt1[2]) ** 2)
使用 numpy 模块进行计算