왜 파이썬의 배열이 느립니까?


153

기대했다 array.array 배열이 언 박싱 것 같다로서, 빠른리스트보다 할 수 있습니다.

그러나 다음과 같은 결과가 나타납니다.

In [1]: import array

In [2]: L = list(range(100000000))

In [3]: A = array.array('l', range(100000000))

In [4]: %timeit sum(L)
1 loop, best of 3: 667 ms per loop

In [5]: %timeit sum(A)
1 loop, best of 3: 1.41 s per loop

In [6]: %timeit sum(L)
1 loop, best of 3: 627 ms per loop

In [7]: %timeit sum(A)
1 loop, best of 3: 1.39 s per loop

그러한 차이의 원인은 무엇입니까?


4
numpy 도구는 효율적으로 배열을 활용할 수 있습니다. % timeit np.sum (A) : 100 루프, 루프 당 최대 3 : 8.87 ms
BM

6
array패키지 를 사용해야하는 상황을 본 적이 없습니다 . 상당한 양의 수학을 원한다면 Numpy는 광속 (예 : C)으로 작동하며 일반적으로)와 같은 순진한 구현보다 낫습니다 sum().
Nick T

40
유권자 닫기 : 왜이 ​​의견에 근거한 것입니까? OP는 측정 가능하고 반복 가능한 현상에 대한 구체적인 기술적 질문을하는 것으로 보입니다.
Kevin

5
@NickT 읽기 최적화 일화 . 알고 보니 arrayA를 정수 (ASCII를 대표하는 바이트)의 문자열을 변환 꽤 빠른 str객체입니다. Guido 자신은 다른 많은 솔루션을 사용 하여이 문제를 해결했으며 성능에 상당히 놀랐습니다. 어쨌든 이것은 그것이 유용하다는 것을 기억하는 유일한 장소입니다. numpy배열을 다루는 것이 훨씬 낫지 만 타사의 의존성입니다.
Bakuriu

답변:


220

저장 "박스 없음"하지만, 때마다 당신은 그것으로 무엇을하기 위해 (일반 파이썬 객체를 포함) "상자"에 파이썬이있는 요소에 액세스 할 수 있습니다. 예를 들어, sum(A)배열을 반복하고 일반 Python int객체 에서 한 번에 하나씩 각 정수를 상자에 넣습니다 . 시간이 걸립니다. 에서 귀하 sum(L)의 모든 권투는 목록을 만들 때 수행되었습니다.

따라서 결국 배열은 일반적으로 느리지 만 메모리는 훨씬 적게 필요합니다.


다음은 최신 버전의 Python 3의 관련 코드이지만 Python이 처음 출시 된 이후 모든 CPython 구현에 동일한 기본 아이디어가 적용됩니다.

목록 항목에 액세스하는 코드는 다음과 같습니다.

PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
    /* error checking omitted */
    return ((PyListObject *)op) -> ob_item[i];
}

아주 적은 것이 있습니다 : 목록에서 '번째 객체를 somelist[i]반환합니다 i(CPython의 모든 Python 객체는 초기 세그먼트가 레이아웃의 레이아웃을 따르는 구조체에 대한 포인터입니다struct PyObject ).

다음 __getitem__arraywith type code 구현입니다 l.

static PyObject *
l_getitem(arrayobject *ap, Py_ssize_t i)
{
    return PyLong_FromLong(((long *)ap->ob_item)[i]);
}

원시 메모리는 플랫폼 고유 C long정수 의 벡터로 처리됩니다 . i'일이 C long읽어되며, 다음 PyLong_FromLong()( "상자") 기본 포장이라고 C long파이썬의 long사이에 파이썬 2의 구분을 제거 파이썬 3에서, 오브젝트 ( int그리고 long, 실제로 형식으로 표시됩니다int ).

이 권투는 파이썬 int객체에 새로운 메모리를 할당 하고 네이티브 C long비트를 그 안에 뿌려야합니다. 원래 예제와 관련하여이 개체의 수명은 매우 짧 sum()으며 (내용을 누적 합계에 추가 하기에 충분한 시간) 새 int개체 를 할당 해제하는 데 더 많은 시간이 필요 합니다.

이것은 CPython 구현에서 속도 차이가 발생하고 항상 발생하며 항상 발생하는 위치입니다.


87

Tim Peters의 탁월한 답변에 추가하기 위해 배열은 버퍼 프로토콜을 구현 하지만 목록은 그렇지 않습니다. 즉, C 확장 (또는 Cython 모듈 작성과 같은 도덕적 동등 물)을 작성하는 경우 Python이 할 수있는 것보다 훨씬 빠르게 배열 요소에 액세스하고 작업 할 수 있습니다. 이것은 상당한 속도 향상을 제공 할 것입니다. 그러나 여러 가지 단점이 있습니다.

  1. 이제 파이썬 대신 C를 쓰는 일을하고 있습니다. Cython은이를 개선하는 한 가지 방법이지만 언어 간의 근본적인 차이점을 제거하지는 않습니다. C 시맨틱에 익숙하고 그것이 무엇을하고 있는지 이해해야합니다.
  2. PyPy의 C API는 어느 정도 작동 하지만 빠르지는 않습니다. PyPy를 대상으로하는 경우 일반 목록으로 간단한 코드를 작성한 다음 JITter가이를 최적화하도록해야합니다.
  3. C 확장은 컴파일해야하기 때문에 순수 Python 코드보다 배포하기가 어렵습니다. 컴파일은 아키텍처 및 운영 체제에 따라 달라지는 경향이 있으므로 대상 플랫폼에 맞게 컴파일해야합니다.

C 확장으로 바로 이동하면 사용 사례에 따라 슬레지 해머를 사용하여 비행을 할 수 있습니다. 먼저 NumPy를 조사 하고 수행하려는 모든 수학을 수행하기에 충분한 지 확인해야합니다. 올바르게 사용하면 네이티브 Python보다 훨씬 빠릅니다.


10

Tim Peters는 이것이 느린 대답 했지만 개선 방법을 살펴 보겠습니다 .

귀하의 예를 고수하십시오 sum(range(...))(여기에서 메모리에 맞추기 위해 귀하의 예보다 요소 10이 작습니다)

import numpy
import array
L = list(range(10**7))
A = array.array('l', L)
N = numpy.array(L)

%timeit sum(L)
10 loops, best of 3: 101 ms per loop

%timeit sum(A)
1 loop, best of 3: 237 ms per loop

%timeit sum(N)
1 loop, best of 3: 743 ms per loop

이 방법은 또한 numpy가 추가 오버 헤드가있는 박스 / 박스를 해제해야합니다. 빨리 만들려면 numpy c 코드 내에 있어야합니다.

%timeit N.sum()
100 loops, best of 3: 6.27 ms per loop

따라서 목록 솔루션에서 numpy 버전까지는 런타임에서 요소 16입니다.

이러한 데이터 구조를 만드는 데 걸리는 시간도 확인하십시오

%timeit list(range(10**7))
1 loop, best of 3: 283 ms per loop

%timeit array.array('l', range(10**7))
1 loop, best of 3: 884 ms per loop

%timeit numpy.array(range(10**7))
1 loop, best of 3: 1.49 s per loop

%timeit numpy.arange(10**7)
10 loops, best of 3: 21.7 ms per loop

우승자 : Numpy

또한 데이터 구조를 만드는 데는 합산하는 데 많은 시간이 걸립니다. 메모리 할당 속도가 느립니다.

그것들의 메모리 사용량 :

sys.getsizeof(L)
90000112
sys.getsizeof(A)
81940352
sys.getsizeof(N)
80000096

따라서 이들은 다양한 오버 헤드로 숫자 당 8 바이트를 사용합니다. 우리가 사용하는 범위에는 32 비트 정수가 충분하므로 일부 메모리를 안전하게 보호 할 수 있습니다.

N=numpy.arange(10**7, dtype=numpy.int32)

sys.getsizeof(N)
40000096

%timeit N.sum()
100 loops, best of 3: 8.35 ms per loop

그러나 64 비트 정수를 추가하면 내 컴퓨터의 32 비트 정수보다 빠르므로 메모리 / 대역폭에 의해 제한되는 경우에만 가치가 있습니다.


-1

참고 100000000로 동일 10^8하지 10^7, 나의 결과는 folowwing과 같습니다를 :

100000000 == 10**8

# my test results on a Linux virtual machine:
#<L = list(range(100000000))> Time: 0:00:03.263585
#<A = array.array('l', range(100000000))> Time: 0:00:16.728709
#<L = list(range(10**8))> Time: 0:00:03.119379
#<A = array.array('l', range(10**8))> Time: 0:00:18.042187
#<A = array.array('l', L)> Time: 0:00:07.524478
#<sum(L)> Time: 0:00:01.640671
#<np.sum(L)> Time: 0:00:20.762153
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.