Python 3에서 x ** 4.0이 x ** 4보다 빠른 이유는 무엇입니까?


164

x**4.0보다 빠릅 x**4니까? CPython 3.5.2를 사용하고 있습니다.

$ python -m timeit "for x in range(100):" " x**4.0"
  10000 loops, best of 3: 24.2 usec per loop

$ python -m timeit "for x in range(100):" " x**4"
  10000 loops, best of 3: 30.6 usec per loop

나는 그것이 어떻게 작동하는지보기 위해 제기 한 힘을 변경하려고 시도했습니다. 예를 들어 x를 10 또는 16의 거듭 제곱으로 올리면 30에서 35로 점프하지만 플로트 로 10.0 씩 올리면 그냥 움직입니다. 약 24.1 ~ 4.

부동 변환 및 2의 거듭 제곱과 관련이 있다고 생각하지만 실제로는 모르겠습니다.

두 경우 모두 2의 거듭 제곱이 빠르다는 것을 알았습니다. 그러한 계산이 통역사 / 컴퓨터에 더 기본적 / 용이하기 때문에 추측합니다. 그러나 여전히 수레는 거의 움직이지 않습니다. 2.0 => 24.1~4 & 128.0 => 24.1~4 그러나 2 => 29 & 128 => 62


TigerhawkT3 은 루프 외부에서는 발생하지 않는다고 지적했습니다. 나는 확인하고 상황은 (내가 본 것에서) 베이스 가 올라갈 때만 발생합니다 . 그것에 대한 아이디어가 있습니까?


11
그것은 가치가 무엇인지를 들어 : 나를 위해 파이썬 2.7.13 요인 2 ~ 3 빠른, 그리고 쇼 역 동작 : 정수 지수가 빠른 부동 소수점 지수보다.

4
@Evert yup,에 대해 14 usec을 얻었 x**4.0습니다 x**4.
dabadaba

답변:


161

x**4.0 빨리 보다는 x**4파이썬 3 * ?

파이썬 3 int객체는 임의의 크기를 지원하도록 설계된 본격적인 객체입니다. 그 사실 때문에 C 수준에서 그대로 처리됩니다 (모든 변수가 PyLongObject *유형으로 선언되는 방법 참조 long_pow). 이것은 또한 지수 훨씬 더하게 까다 하고 지루한을 당신이 함께 놀러 할 필요가 있기 때문에 ob_digit그것을 수행하기 위해 그 값을 표현하기 위해 사용하는 배열입니다. ( 출처 용감한합니다. - 참조 : 파이썬에서 큰 정수에 대한 이해 메모리 할당 에 대한 자세한 내용은 PyLongObject.들)

float반대로 Python 객체는 (를 사용하여 ) C 유형 으로 변환 할 수 있으며 해당 기본 유형을 사용하여 작업을 수행 할 수 있습니다 . 이것은 대단한 관련 에지 경우에 확인 후, 그것은 파이썬을 허용하기 때문에 플랫폼 '를 사용 ( C의 입니다 ) 실제 지수를 처리하는 :doublePyFloat_AsDoublepowpow

/* Now iv and iw are finite, iw is nonzero, and iv is
 * positive and not equal to 1.0.  We finally allow
 * the platform pow to step in and do the rest.
 */
errno = 0;
PyFPE_START_PROTECT("pow", return NULL)
ix = pow(iv, iw); 

iviw우리의 원래있는 PyFloatObjectC 등의 double의.

그만한 가치가있는 것은 : Python 2.7.13for me는 2~3더 빠르며 반대 행동을 보여줍니다.

이전의 사실 은 또한 파이썬 2와 3의 불일치를 설명 하므로 흥미 롭기 때문에이 의견도 다루겠다고 생각했습니다.

Python 2에서는 Python 3 intint객체 와 다른 오래된 객체를 사용하고 있습니다 ( int3.x의 모든 객체는 PyLongObject유형 임). Python 2에는 객체의 값에 따라 (또는 접미사를 사용하는 경우) 구별되는 것이 있습니다 L/l.

# Python 2
type(30)  # <type 'int'>
type(30L) # <type 'long'>

<type 'int'>여기 참조는 같은 일을 수행 float의 수행을 가 안전하게 C로 변환됩니다, long 지수가 그것을 수행 합니다 (이 int_pow그렇게 할 수 있다면 그 때문에, 또한 레지스터에 그들을 넣어 컴파일러 힌트 차이를 만들) :

static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
    register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */    

이것은 좋은 속도 이득을 허용합니다.

방법 부진 보려면 <type 'long'>비교에의 <type 'int'>당신은, 랩 된 경우의 xA의 이름 long(기본적으로 사용하도록 강제 파이썬 2에서 전화를 long_pow파이썬 3과), 속도 이득이 사라집니다 :

# <type 'int'>
(python2)  python -m timeit "for x in range(1000):" " x**2"       
10000 loops, best of 3: 116 usec per loop
# <type 'long'> 
(python2)  python -m timeit "for x in range(1000):" " long(x)**2"
100 loops, best of 3: 2.12 msec per loop

메모를 받아, 그 한 조각을 변환하는 비록 intlong(@pydsinger가 가리키는 아웃과 같은) 다른 하나는하지 않지만,이 캐스트는 둔화 뒤에 기여 힘이 아니다. 의 구현입니다 long_pow. (본문 만 long(x)볼 시간 ).

[...] 루프 외부에서는 발생하지 않습니다. [...] 그것에 대한 아이디어가 있습니까?

이것은 CPython의 들여다 보는 구멍 최적화 프로그램으로 상수를 접습니다. 지수 결과를 찾기위한 실제 계산이없고 값만로드되므로 두 경우 모두 동일한 정확한 타이밍을 얻습니다.

dis.dis(compile('4 ** 4', '', 'exec'))
  1           0 LOAD_CONST               2 (256)
              3 POP_TOP
              4 LOAD_CONST               1 (None)
              7 RETURN_VALUE

동일한 바이트 코드는 int 대신 float 를 로드 '4 ** 4.'한다는 점만 다릅니다 .LOAD_CONST256.0256

dis.dis(compile('4 ** 4.', '', 'exec'))
  1           0 LOAD_CONST               3 (256.0)
              2 POP_TOP
              4 LOAD_CONST               2 (None)
              6 RETURN_VALUE

따라서 시간은 동일합니다.


* 위의 모든 내용은 Python의 참조 구현 인 CPython에만 적용됩니다. 다른 구현은 다르게 수행 될 수 있습니다.


그것이 무엇이든간에 range, **연산 자체의 타이밍만으로 정수와 부동 소수점 사이에 차이가 없으므로 a를 통한 루프와 관련이 있습니다.
TigerhawkT3

차이는 변수를 찾을 때만 나타나며 ( ) 4**4속도가 빠르며이 4**4.0답변은 그 변수를 전혀 다루지 않습니다.
TigerhawkT3

1
그러나 상수는 @ TigerhawkT3 ( dis(compile('4 ** 4', '', 'exec')))로 접히 므로 시간이 정확히 동일 해야합니다 .
Dimitris Fasarakis Hilliard

마지막 타이밍은 당신이하는 말을 보여주지 않는 것 같습니다. long(x)**2.여전히 long(x)**24-5 배 보다 빠릅니다 . (하지만 downvoters 중 하나는 아닙니다)
Graipher

3
@ mbomb007 <type 'long'>파이썬 3 에서 유형을 제거하는 것은 아마도 언어를 단순화하려는 노력으로 설명 될 것입니다. 정수를 나타내는 하나의 유형을 가질 수있는 경우 2보다 관리하기 쉽습니다 (필요할 때 하나를 다른 것으로 변환하는 것에 대해 걱정하고 사용자가 혼란스러워하는 등). 속도 게인은 그 다음으로 부차적입니다. PEP 237 의 이론적 근거 는 또한 더 많은 통찰력을 제공합니다.
Dimitris Fasarakis Hilliard 2012

25

바이트 코드를 보면 표현식이 완전히 동일하다는 것을 알 수 있습니다. 유일한 차이점은의 인수가 될 상수 유형입니다 BINARY_POWER. 따라서 가장 확실한 이유 int는 선 아래로 부동 소수점 숫자로 변환 되었기 때문 입니다.

>>> def func(n):
...    return n**4
... 
>>> def func1(n):
...    return n**4.0
... 
>>> from dis import dis
>>> dis(func)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4)
              6 BINARY_POWER
              7 RETURN_VALUE
>>> dis(func1)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4.0)
              6 BINARY_POWER
              7 RETURN_VALUE

업데이트 : CPython 소스 코드 에서 Objects / abstract.c 를 살펴 보겠습니다 .

PyObject *
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
{
    return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()");
}

PyNumber_Powerternary_op여기에 붙여 넣기에 너무 긴 호출 이므로 링크가 있습니다.

그것은 호출 nb_power의 슬롯을 x전달 y인자로.

마지막으로 Objects / floatobject.c의float_pow() 686 행에서 인수가 실제 작업 직전에 C로 변환되는 것을 볼 수 있습니다.double

static PyObject *
float_pow(PyObject *v, PyObject *w, PyObject *z)
{
    double iv, iw, ix;
    int negate_result = 0;

    if ((PyObject *)z != Py_None) {
        PyErr_SetString(PyExc_TypeError, "pow() 3rd argument not "
            "allowed unless all arguments are integers");
        return NULL;
    }

    CONVERT_TO_DOUBLE(v, iv);
    CONVERT_TO_DOUBLE(w, iw);
    ...

1
@ Jean-FrançoisFabre 나는 그것이 일정한 접힘 때문이라고 생각합니다.
Dimitris Fasarakis Hilliard

2
나는 전환이 있고 그것들이 "가장 확실하게"다르게 처리되지 않았다는 의미는 소스가없는 약간의 확장이라고 생각합니다.
miradulo

1
@Mitch-특히이 특정 코드에서이 두 작업의 실행 시간에는 차이가 없습니다. 차이점은 OP 루프에서만 발생합니다. 이 답변은 결론에 도달합니다.
TigerhawkT3

2
float_pow느린 경우에도 실행되지 않을 때만보고 있습니까?
user2357112는

2
@ TigerhawkT3 : 4**44**4.0일정이 접혀 얻을. 그것은 완전히 별개의 효과입니다.
user2357112는 Monica

-1

하나는 정확하기 때문에 다른 것은 근사치입니다.

>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625

왜 그 다운 보더가 다운 보트를했는지 모르겠지만이 답변이 질문에 답하지 않기 때문에 그렇게했습니다. 무언가가 옳다고해서 그것이 더 빠르거나 느리다는 것을 의미하지는 않습니다. 하나는 다른 유형보다 느립니다. 하나는 C 유형으로 작업 할 수 있고 다른 하나는 Python 객체로 작업해야하기 때문입니다.
Dimitris Fasarakis Hilliard

1
설명 주셔서 감사합니다. 글쎄, 나는 정확히 모든 숫자를 정확하게 계산하는 것보다 숫자를 대략 12 자릿수로 계산하는 것이 더 빠르다는 것이 분명하다고 생각했습니다. 어쨌든 근사값을 사용하는 유일한 이유는 계산이 더 빠르기 때문입니다.
Veky
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.