파이썬에서 성능의 측면에서, 목록 – 이해, 또는 기능이 좋아 map()
, filter()
및 reduce()
빠른 루프에 대한보다 더? 기술적 으로 C 속도로 실행되는 반면 for 루프는 파이썬 가상 머신 속도로 실행되는 이유는 무엇 입니까?
개발중인 게임에서 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
또는 대신 목록 이해 filter
)이 약간 더 빠릅니다.
개발중인 게임에서 for 루프를 사용하여 복잡하고 거대한 맵을 그려야한다고 가정 해 봅시다. 예를 들어, 목록 이해가 실제로 더 빠르면 지연을 피하기 위해 훨씬 더 나은 옵션이 될 것입니다 (코드의 시각적 복잡성에도 불구하고).
“최적화되지 않은”좋은 파이썬으로 작성했을 때 이와 같은 코드가 아직 충분히 빠르지 않다면, 파이썬 수준의 마이크로 최적화가 충분히 빠르지 않아 C로 떨어질 생각을 시작해야 할 것입니다. 마이크로 최적화는 종종 파이썬 코드의 속도를 크게 향상시킬 수 있습니다. 또한, 한도에 도달하기 전에도 총알을 물고 C를 쓰는 것이 더 비용 효율적입니다 (동일한 노력으로 속도 15 % 향상 vs. 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-loop보다 약간 느리게 나타났습니다 (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
추가를 사용합니다. 물론 어느 정도 느립니다.- 목록 이해력은 목록을 작성하는 데 가장 적은 시간을 소비했을뿐만 아니라
sum
대조적으로 훨씬 빠릅니다.map
답변
Alphii answer에 트위스트를 추가하면 실제로 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 루프와 맵을 같은 용어로 사용하면 실제로 사실입니다. 람다는 기능적인 개념이며 이론적으로 부작용이 없어야하지만,에 추가하는 것과 같은 부작용이 있을 수 있습니다 a
. 이 경우 Python 3.6.1, Ubuntu 14.04, Intel (R) Core 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의 코드 중 일부를 수정 하고 List comprehension이 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