목록 이해와 기능적 기능이 "for loop"보다 빠릅니까?


155

파이썬에서 성능의 측면에서, 목록 - 이해, 또는 기능이 좋아 map(), filter()reduce()빠른 루프에 대한보다 더? 기술적 으로 C 속도로 실행되는 반면 for 루프는 파이썬 가상 머신 속도로 실행되는 이유는 무엇 입니까?

개발중인 게임에서 for 루프를 사용하여 복잡하고 거대한 맵을 그려야한다고 가정 해 봅시다. 예를 들어, 목록 이해가 실제로 더 빠르면 지연을 피하기 위해 훨씬 더 나은 옵션이 될 것입니다 (코드의 시각적 복잡성에도 불구하고).

답변:


146

다음은 경험을 바탕으로 한 대략적인 지침과 교육 된 추측입니다. 당신은해야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 % 속도 향상).


25

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.


17
이 페이지는 잘 읽히고 부분적으로 관련되어 있지만 그 숫자를 인용하는 것만으로는 도움이되지 않으며 오해의 소지가 있습니다.

1
이것은 당신이 무엇을 타이밍에 대한 표시를 제공하지 않습니다. 상대 성능은 loop / listcomp / map의 내용에 따라 크게 달라질 수 있습니다.
user2357112는

@delnan 동의합니다. OP가 성능 차이를 이해하기 위해 설명서를 읽도록 촉구하기 위해 답변을 수정했습니다.
Anthony Kong

@ user2357112 컨텍스트에 링크 된 위키 페이지를 읽어야합니다. OP의 참조를 위해 게시했습니다.
앤서니 콩

13

당신은 특별히에 대한 질문 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')

2
이 질문에 대답하는 다소 복잡한 방법처럼 보입니다. 좀 더 이해하기 쉽게 정리할 수 있습니까?
Aaron Hall

2
@AaronHall andreipmbcn의 대답은 사소한 예가 아니기 때문에 오히려 흥미 롭습니다. 우리가 놀 수있는 코드.
Anthony Kong

@AaronHall, 텍스트 단락이 더 명확하고 간단하게 들리도록 편집하겠습니까, 아니면 코드를 편집하겠습니까?
andreipmbcn

9

나는 속도를 테스트하는 간단한 스크립트를 작성했으며 이것이 내가 찾은 것입니다. 실제로 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

파이썬 3.6.1에서는 차이가 그리 크지 않습니다. Reduce and Map은 0.24로 내려 가고 이해도는 0.29로 표시됩니다. 0.18에서 더 높습니다.
jjmerelo

intin을 제거하면 square_sum4for 루프보다 약간 빠르며 느려집니다.
jjmerelo

6

@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

5

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

2
square_sum3 및 square_sum4가 올바르지 않습니다. 그들은 합계를주지 않을 것입니다. @alisca chen의 아래 답변은 실제로 정확합니다.
ShikharDua

3

@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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.