2017-09-12 1 views
6

필자는 외부 종속성이없는 pure-python을 두 시퀀스의 요소별로 비교하려고했습니다. 내 최초의 솔루션이었다 : 지도 대 Starmap의 성능?

list(map(operator.eq, seq1, seq2)) 

은 그 때 나는 나에게 꽤 비슷한 것 같았다, itertools에서 starmap 기능을 발견했다. 그러나 최악의 경우 컴퓨터에서 37 % 빨랐습니다. 그것이 나에게 분명하지 않았다, 나는이 발전기에서 한 요소를 검색하는 데 필요한 시간을 측정 (이 방법이 정확한지 모르겠다) : 요소를 두 번째 솔루션을 검색에서

from operator import eq 
from itertools import starmap 

seq1 = [1,2,3]*10000 
seq2 = [1,2,3]*10000 
seq2[-1] = 5 

gen1 = map(eq, seq1, seq2)) 
gen2 = starmap(eq, zip(seq1, seq2)) 

%timeit -n1000 -r10 next(gen1) 
%timeit -n1000 -r10 next(gen2) 

271 ns ± 1.26 ns per loop (mean ± std. dev. of 10 runs, 1000 loops each) 
208 ns ± 1.72 ns per loop (mean ± std. dev. of 10 runs, 1000 loops each) 

이 24 % 더 성능이 좋은이다 . 그 후에 그들은 모두 list에 대해 동일한 결과를 산출합니다. 그러나 어딘가에서 우리는 시간에 추가로 13 %를 얻을 :

%timeit list(map(eq, seq1, seq2)) 
%timeit list(starmap(eq, zip(seq1, seq2))) 

5.24 ms ± 29.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 
3.34 ms ± 84.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 

나는 그런 중첩 된 코드의 프로파일에 깊이 파고하는 방법을 몰라? 그래서 제 질문은 왜 우리가 list 함수에서 13 %의 추가 이득을 얻을 수 있는지 검색하는 첫 번째 생성기가 그렇게 빠른 이유입니까?

EDIT : alllist 함수로 대체 나의 제 의도 대신 all의 요소 와이즈 비교를 수행 하였다. 이 대체는 타이밍 비율에 영향을주지 않습니다. 윈도우 10 (64 비트)에

CPython의 3.6.2 내가 알 수

+1

왜 그냥 사용하지 ... 호출 된 함수는 C 함수 때 zipstarmap 여러 인수 map를 호출하는 것보다 빠르다는 것을 점에 이르기까지의 "우연"의 많은있다'seq1 = = seq2'? –

+0

@ Błotosmętek 정정에 감사드립니다! 나의 첫 번째 의도는'all' 대신에 element-wise 비교였다. 나의 질문에서 분명하지 않았다. 정말로'all' 대신'list'를 사용하면 시간 순서가 동일해질 것이다. – godaygo

+0

파이썬 버전은 무엇입니까? 그리고이 CPython은 무엇입니까? – MSeifert

답변

2

관측 성능 차이 (함께) 기여하는 여러 요소가있다 :

  • zip 다시 사용하는 것이 하나의 기준 카운트를 가지면 리턴 tuple을 다음 __next__ 콜 만들어집니다.
  • map__next__ 호출이있을 때마다 "매핑 된 함수"에 전달되는 tuple을 만듭니다. 사실 파이썬은 사용되지 않는 튜플에 대한 저장소를 유지하기 때문에 실제로는 처음부터 새로운 튜플을 생성하지는 않습니다. 그러나이 경우 map은 올바른 크기의 사용되지 않은 튜플을 찾아야합니다.
  • starmap iterable의 다음 항목이 tuple 유형인지 확인한 후 해당 항목을 전달합니다.
  • PyObject_Call으로 C 코드에서 C 함수를 호출해도 호출 수신자에게 전달되는 새 튜플이 생성되지 않습니다.

그래서 zipstarmap는 또 다시 하나 개의 튜플을 사용하는 것이다 따라서 대단히 함수 호출 오버 헤드를 줄일 수 operator.eq 전달됩니다. 반면 mapoperator.eq이 호출 될 때마다 새로운 튜플을 생성합니다 (또는 CPython 3.6에서 C 배열 채우기). 실제로 속도 차이는 튜플 생성 오버 헤드 일뿐입니다. "간단한 Py_DECREF가 충분,

In [1]: %load_ext cython 

In [2]: %%cython 
    ...: 
    ...: from cpython.ref cimport Py_DECREF 
    ...: 
    ...: cpdef func(zipper): 
    ...:  a = next(zipper) 
    ...:  print('a', a) 
    ...:  Py_DECREF(a) 
    ...:  b = next(zipper) 
    ...:  print('a', a) 

In [3]: func(zip([1, 2], [1, 2])) 
a (1, 1) 
a (2, 2) 

예, tuple의 정말 불변하지 :

대신 소스 코드에 링크의 나는 이것을 확인하는 데 사용할 수있는 몇 가지 사이 썬 코드를 제공합니다 트릭 "zip 다른 사람이 반환 된 튜플에 대한 참조를 보유하고 있다고 믿으십시오! 은 "튜플 패스 스루"에 관해서는

:

그래서 튜플이 오른쪽 (이들은 C의 함수로 정의해서!)이 순수 파이썬 기능 발생하지 않습니다 통과
In [4]: %%cython 
    ...: 
    ...: def func_inner(*args): 
    ...:  print(id(args)) 
    ...: 
    ...: def func(*args): 
    ...:  print(id(args)) 
    ...:  func_inner(*args) 

In [5]: func(1, 2) 
1404350461320 
1404350461320 

:

In [8]: %%cython 
    ...: 
    ...: def func_inner_c(*args): 
    ...:  print(id(args)) 
    ...: 
    ...: def func(inner, *args): 
    ...:  print(id(args)) 
    ...:  inner(*args) 
    ...: 

In [9]: def func_inner_py(*args): 
    ...:  print(id(args)) 
    ...: 
    ...: 

In [10]: func(func_inner_py, 1, 2) 
1404350471944 
1404353010184 

In [11]: func(func_inner_c, 1, 2) 
1404344354824 
1404344354824 
: 호출 된 함수가 C 함수를 호출하는 경우에도 C 함수가 아닌 경우 그것은 또한 발생하지 않습니다

In [6]: def func_inner(*args): 
    ...:  print(id(args)) 
    ...: 
    ...: def func(*args): 
    ...:  print(id(args)) 
    ...:  func_inner(*args) 
    ...: 

In [7]: func(1, 2) 
1404350436488 
1404352833800 

그래서

1

한 가지 차이점은 map이 반복 가능 객체에서 항목을 검색하는 방법입니다. mapzip은 전달 된 각 iterable에서 iterators의 tuple을 생성합니다. 이제 zip은 내부적으로 result tuple을 유지 관리하며, 다음 번 호출 할 때마다 채워지 며 다른 한편으로는 다음 호출마다 map creates a new array*이 채워지고 할당이 해제됩니다.


* 으로 새로운 파이썬 튜플 매번를 할당하는 데 사용 3.5.4 map_next까지 MSeifert에 의해 지적했다. 이것은 3.6에서 변경되었고 5 iterables C 스택이 사용되었고 그보다 큰 힙은 사용되었다. 관련 PR : Issue #27809: map_next() uses fast callAdd _PY_FASTCALL_SMALL_STACK constant | 문제 : https://bugs.python.org/issue27809

+0

이것은 3.6이라고 가정합니다. [3.5.4] (https://github.com/python/cpython/blob/v3)의 코드에 유의하십시오. .5.4/Python/bltinmodule.C# L1168-L1192)가 다르게 보입니다. :) – MSeifert

+0

@MSeifert 얼마나 느리고/빠른 3.5.4 구현이 3.6.2와 비교되었는지 궁금합니다. –

+0

(heap 대 C-Stack 액세스로 인해 5 iterables까지 느려야합니다.) –