就 Python 的性能而言,列表理解或类似 的函数是否map()
比for 循环更快?为什么,从技术上讲,它们以 C 速度运行,而for 循环以 python 虚拟机速度运行?filter()
reduce()
假设在我正在开发的游戏中,我需要使用 for 循环绘制复杂而巨大的地图。这个问题肯定是相关的,因为例如,如果列表理解确实更快,那么这将是一个更好的选择,以避免滞后(尽管代码的视觉复杂性)。
就 Python 的性能而言,列表理解或类似 的函数是否map()
比for 循环更快?为什么,从技术上讲,它们以 C 速度运行,而for 循环以 python 虚拟机速度运行?filter()
reduce()
假设在我正在开发的游戏中,我需要使用 for 循环绘制复杂而巨大的地图。这个问题肯定是相关的,因为例如,如果列表理解确实更快,那么这将是一个更好的选择,以避免滞后(尽管代码的视觉复杂性)。
以下是粗略的指导方针和基于经验的有根据的猜测。您应该timeit
或分析您的具体用例以获得硬数字,这些数字可能偶尔会与以下内容不一致。
列表推导通常比精确等效的for
循环(实际构建列表)快一点,很可能是因为它不必append
在每次迭代时查找列表及其方法。但是,列表推导仍然执行字节码级别的循环:
>>> dis.dis(<the code object for `[x for x in range(10)]`>)
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (x)
12 LOAD_FAST 1 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
使用列表推导代替不构建列表的循环,毫无意义地累积无意义值的列表然后丢弃列表,由于创建和扩展列表的开销,通常会更慢。列表推导并不是天生就比一个好的旧循环更快的魔法。
至于功能列表处理函数:虽然这些是用 C 编写的,并且可能优于用 Python 编写的等效函数,但它们不一定是最快的选择。如果该函数也是用 C 编写的,则预计会有所加快。但是大多数情况下使用一个lambda
(或其他 Python 函数),重复设置 Python 堆栈帧等的开销会消耗掉任何节省。简单地在线执行相同的工作,没有函数调用(例如列表理解而不是map
or filter
)通常会稍微快一些。
假设在我正在开发的游戏中,我需要使用 for 循环绘制复杂而巨大的地图。这个问题肯定是相关的,因为例如,如果列表理解确实更快,那么这将是一个更好的选择,以避免滞后(尽管代码的视觉复杂性)。
很有可能,如果这样的代码在用良好的非“优化”Python 编写时还不够快,那么再多的 Python 级别的微优化也无法使其足够快,您应该开始考虑使用 C 语言。微优化通常可以大大加快 Python 代码的速度,对此有一个较低的(绝对值)限制。此外,即使在你达到这个上限之前,咬紧牙关写一些 C 语言也会变得更加经济高效(15% 的加速与 300% 的加速)。
如果您查看python.org 上的信息,您可以看到以下摘要:
Version Time (seconds)
Basic loop 3.47
Eliminate dots 2.45
Local variable & no dots 1.79
Using map function 0.54
但是您确实应该详细阅读上述文章以了解性能差异的原因。
我还强烈建议您使用timeit来计时您的代码。归根结底,可能会出现这样一种情况,例如,您可能需要在for
满足条件时跳出循环。它可能比通过调用找出结果更快map
。
您专门询问map()
,filter()
和reduce()
,但我假设您想了解一般的函数式编程。在计算一组点中所有点之间的距离的问题上,我自己对此进行了测试,结果发现函数式编程(使用starmap
内置itertools
模块中的函数)比 for 循环稍慢(耗时 1.25 倍,在事实)。这是我使用的示例代码:
import itertools, time, math, random
class Point:
def __init__(self,x,y):
self.x, self.y = x, y
point_set = (Point(0, 0), Point(0, 1), Point(0, 2), Point(0, 3))
n_points = 100
pick_val = lambda : 10 * random.random() - 5
large_set = [Point(pick_val(), pick_val()) for _ in range(n_points)]
# the distance function
f_dist = lambda x0, x1, y0, y1: math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2)
# go through each point, get its distance from all remaining points
f_pos = lambda p1, p2: (p1.x, p2.x, p1.y, p2.y)
extract_dists = lambda x: itertools.starmap(f_dist,
itertools.starmap(f_pos,
itertools.combinations(x, 2)))
print('Distances:', list(extract_dists(point_set)))
t0_f = time.time()
list(extract_dists(large_set))
dt_f = time.time() - t0_f
功能版比程序版快吗?
def extract_dists_procedural(pts):
n_pts = len(pts)
l = []
for k_p1 in range(n_pts - 1):
for k_p2 in range(k_p1, n_pts):
l.append((pts[k_p1].x - pts[k_p2].x) ** 2 +
(pts[k_p1].y - pts[k_p2].y) ** 2)
return l
t0_p = time.time()
list(extract_dists_procedural(large_set))
# using list() on the assumption that
# it eats up as much time as in the functional version
dt_p = time.time() - t0_p
f_vs_p = dt_p / dt_f
if f_vs_p >= 1.0:
print('Time benefit of functional progamming:', f_vs_p,
'times as fast for', n_points, 'points')
else:
print('Time penalty of functional programming:', 1 / f_vs_p,
'times as slow for', n_points, 'points')
我写了一个简单的脚本来测试速度,这就是我发现的。实际上 for 循环在我的情况下是最快的。这真的让我吃惊,看看下面(计算平方和)。
from functools import reduce
import datetime
def time_it(func, numbers, *args):
start_t = datetime.datetime.now()
for i in range(numbers):
func(args[0])
print (datetime.datetime.now()-start_t)
def square_sum1(numbers):
return reduce(lambda sum, next: sum+next**2, numbers, 0)
def square_sum2(numbers):
a = 0
for i in numbers:
i = i**2
a += i
return a
def square_sum3(numbers):
sqrt = lambda x: x**2
return sum(map(sqrt, numbers))
def square_sum4(numbers):
return(sum([int(i)**2 for i in numbers]))
time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
0:00:00.302000 #Reduce
0:00:00.144000 #For loop
0:00:00.318000 #Map
0:00:00.390000 #List comprehension
我修改了@Alisa 的代码并用来cProfile
说明为什么列表理解更快:
from functools import reduce
import datetime
def reduce_(numbers):
return reduce(lambda sum, next: sum + next * next, numbers, 0)
def for_loop(numbers):
a = []
for i in numbers:
a.append(i*2)
a = sum(a)
return a
def map_(numbers):
sqrt = lambda x: x*x
return sum(map(sqrt, numbers))
def list_comp(numbers):
return(sum([i*i for i in numbers]))
funcs = [
reduce_,
for_loop,
map_,
list_comp
]
if __name__ == "__main__":
# [1, 2, 5, 3, 1, 2, 5, 3]
import cProfile
for f in funcs:
print('=' * 25)
print("Profiling:", f.__name__)
print('=' * 25)
pr = cProfile.Profile()
for i in range(10**6):
pr.runcall(f, [1, 2, 5, 3, 1, 2, 5, 3])
pr.create_stats()
pr.print_stats()
结果如下:
=========================
Profiling: reduce_
=========================
11000000 function calls in 1.501 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1000000 0.162 0.000 1.473 0.000 profiling.py:4(reduce_)
8000000 0.461 0.000 0.461 0.000 profiling.py:5(<lambda>)
1000000 0.850 0.000 1.311 0.000 {built-in method _functools.reduce}
1000000 0.028 0.000 0.028 0.000 {method 'disable' of '_lsprof.Profiler' objects}
=========================
Profiling: for_loop
=========================
11000000 function calls in 1.372 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1000000 0.879 0.000 1.344 0.000 profiling.py:7(for_loop)
1000000 0.145 0.000 0.145 0.000 {built-in method builtins.sum}
8000000 0.320 0.000 0.320 0.000 {method 'append' of 'list' objects}
1000000 0.027 0.000 0.027 0.000 {method 'disable' of '_lsprof.Profiler' objects}
=========================
Profiling: map_
=========================
11000000 function calls in 1.470 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1000000 0.264 0.000 1.442 0.000 profiling.py:14(map_)
8000000 0.387 0.000 0.387 0.000 profiling.py:15(<lambda>)
1000000 0.791 0.000 1.178 0.000 {built-in method builtins.sum}
1000000 0.028 0.000 0.028 0.000 {method 'disable' of '_lsprof.Profiler' objects}
=========================
Profiling: list_comp
=========================
4000000 function calls in 0.737 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1000000 0.318 0.000 0.709 0.000 profiling.py:18(list_comp)
1000000 0.261 0.000 0.261 0.000 profiling.py:19(<listcomp>)
1000000 0.131 0.000 0.131 0.000 {built-in method builtins.sum}
1000000 0.027 0.000 0.027 0.000 {method 'disable' of '_lsprof.Profiler' objects}
恕我直言:
reduce
并且map
通常很慢。不仅如此,与使用列表相比,在返回sum
的迭代器上使用很慢map
sum
for_loop
使用 append,当然在一定程度上很慢sum
速度更快map
为Alphii 答案添加一个扭曲,实际上 for 循环将是第二好的,并且比它慢 6 倍map
from functools import reduce
import datetime
def time_it(func, numbers, *args):
start_t = datetime.datetime.now()
for i in range(numbers):
func(args[0])
print (datetime.datetime.now()-start_t)
def square_sum1(numbers):
return reduce(lambda sum, next: sum+next**2, numbers, 0)
def square_sum2(numbers):
a = 0
for i in numbers:
a += i**2
return a
def square_sum3(numbers):
a = 0
map(lambda x: a+x**2, numbers)
return a
def square_sum4(numbers):
a = 0
return [a+i**2 for i in numbers]
time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
主要的变化是消除了缓慢的sum
调用,以及int()
最后一种情况下可能不必要的调用。实际上,将 for 循环和 map 放在相同的术语中使其成为事实。请记住,lambda 是函数概念,理论上不应该有副作用,但是,它们可以有副作用,比如添加到a
. 在这种情况下使用 Python 3.6.1、Ubuntu 14.04、Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz 的结果
0:00:00.257703 #Reduce
0:00:00.184898 #For loop
0:00:00.031718 #Map
0:00:00.212699 #List comprehension
我设法修改了@alpiii 的一些代码,发现列表理解比 for 循环快一点。这可能是由于int()
,列表理解和 for 循环之间不公平。
from functools import reduce
import datetime
def time_it(func, numbers, *args):
start_t = datetime.datetime.now()
for i in range(numbers):
func(args[0])
print (datetime.datetime.now()-start_t)
def square_sum1(numbers):
return reduce(lambda sum, next: sum+next*next, numbers, 0)
def square_sum2(numbers):
a = []
for i in numbers:
a.append(i*2)
a = sum(a)
return a
def square_sum3(numbers):
sqrt = lambda x: x*x
return sum(map(sqrt, numbers))
def square_sum4(numbers):
return(sum([i*i for i in numbers]))
time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
0:00:00.101122 #Reduce
0:00:00.089216 #For loop
0:00:00.101532 #Map
0:00:00.068916 #List comprehension