2017-09-26 1 views
3

나는 독서 오전 itertools recipe for unique_everseen : itertools 레시피에서 seen_add = seen.add를 정의하는 요점은 무엇입니까?

def unique_everseen(iterable, key=None): 
    "List unique elements, preserving order. Remember all elements ever seen." 
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D 
    # unique_everseen('ABBCcAD', str.lower) --> A B C D 
    seen = set() 
    seen_add = seen.add 
    if key is None: 
     for element in filterfalse(seen.__contains__, iterable): 
      seen_add(element) 
      yield element 
    else: 
     for element in iterable: 
      k = key(element) 
      if k not in seen: 
       seen_add(k) 
       yield element 

위의 코드에 seen_add = seen.add를 정의하는 점은 무엇입니까

?

답변

3

성능. (새로운 방법 개체마다 바인딩이있는) 방법은 속성 검색보다 훨씬 빠르다 역 참조 로컬 이름을 사용 :

>>> import timeit 
>>> timeit.timeit('s.add', 's = set()', number=10**7) 
0.4227792940218933 
>>> timeit.timeit('seen_add', 's = set(); seen_add = s.add', number=10**7) 
0.15441945398924872 

을 로컬 기준을 사용하여 빠른 속도로 거의 3 배이다. set.add이 루프에서 사용되기 때문에 속성 조회를 최적화하는 것이 좋습니다. "hoisting" or "Loop-invariant code motion"라는 기술의

2

. 본질적으로 여러 번 실행되지만 항상 루프 본문 대신 루프 외부에서 동일한 값을 반환하는 작업을 수행합니다.

이 경우 루프는 seen 집합의 add 속성을 반복적으로 조회하고 "바운드 방식"을 만듭니다. 사실 꽤 빠르지 만 여전히 루프 내에서 여러 번 수행되는 작업이며 항상 동일한 결과를 제공합니다. 따라서 으로 속성 (이 경우 바인딩 된 메소드)을 한 번 찾아 변수로 저장하여 성능을 향상시킬 수 있습니다.

주이 "많이"의미없이 그것의 속도 업을 제공하면서 그. 당신이 얻을 동안 중복 불가능에 ~ 그래서

# no duplicates 
a = list(range(10000)) 
%timeit list(unique_everseen(a)) 
# 5.73 ms ± 279 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 
%timeit list(unique_everseen_without(a)) 
# 6.81 ms ± 396 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 

# some duplicates 
import random 
a = [random.randint(0, 100) for _ in range(10000)] 
%timeit list(unique_everseen(a)) 
# 1.64 ms ± 12.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 
%timeit list(unique_everseen_without(a)) 
# 1.66 ms ± 16.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

# only duplicates 
a = [1]*10000 
%timeit list(unique_everseen(a)) 
# 1.64 ms ± 78.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 
%timeit list(unique_everseen_without(a)) 
# 1.63 ms ± 24.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

10 %의 속도 향상을 실제로 거의 쓸모 케이스 :

from itertools import filterfalse 

def unique_everseen(iterable): 
    seen = set() 
    seen_add = seen.add 
    for element in filterfalse(seen.__contains__, iterable): 
     seen_add(element) 
     yield element 

def unique_everseen_without(iterable): 
    seen = set() 
    for element in filterfalse(seen.__contains__, iterable): 
     seen.add(element) 
     yield element 

일부 exemplaric 타이밍 : 나는 코드가 짧게하는이 타이밍에 대한 두 번째 분기를 제거 많은 중복이있는 경우입니다.

실제로이 요리법은 "호이 스팅"의 또 다른 예를 보여줍니다. 구체적으로는 filterfalse(seen.__contains__, iterable)입니다. 그러면 방법을 seen 번 찾은 다음 filterfalse 안에 반복적으로 호출합니다.

어쩌면 테이크 아웃해야합니다 : 호이 스팅 방법 조회는 마이크로 최적화입니다. 루프의 상수 요소를 줄입니다. 속도 향상은 특정 작업에서는 가치가있을 수 있지만 개인적으로는 필연적으로 프로파일 링/벤치마킹과 결합하여 사용해야한다고 생각합니다.