pow (a, d, n)이 a ** d % n보다 훨씬 빠른 이유는 무엇입니까?


110

Miller-Rabin 소수성 테스트 를 구현하려고했는데 왜 중형 숫자 (~ 7 자리)에 너무 오래 (> 20 초) 걸리는지 의아해했습니다. 결국 문제의 원인이되는 다음 코드 줄을 발견했습니다.

x = a**d % n

(단 a, dn모든 유사하지만 동일하지 않은, 중간 번호는, **누승 연산자이며, %모듈러 연산자이다)

그런 다음 다음으로 바꾸려고 시도했습니다.

x = pow(a, d, n)

비교하면 거의 즉각적입니다.

컨텍스트를 위해 원래 기능은 다음과 같습니다.

from random import randint

def primalityTest(n, k):
    if n < 2:
        return False
    if n % 2 == 0:
        return False
    s = 0
    d = n - 1
    while d % 2 == 0:
        s += 1
        d >>= 1
    for i in range(k):
        rand = randint(2, n - 2)
        x = rand**d % n         # offending line
        if x == 1 or x == n - 1:
            continue
        for r in range(s):
            toReturn = True
            x = pow(x, 2, n)
            if x == 1:
                return False
            if x == n - 1:
                toReturn = False
                break
        if toReturn:
            return False
    return True

print(primalityTest(2700643,1))

시간 제한 계산의 예 :

from timeit import timeit

a = 2505626
d = 1520321
n = 2700643

def testA():
    print(a**d % n)

def testB():
    print(pow(a, d, n))

print("time: %(time)fs" % {"time":timeit("testA()", setup="from __main__ import testA", number=1)})
print("time: %(time)fs" % {"time":timeit("testB()", setup="from __main__ import testB", number=1)})

출력 (PyPy 1.9.0으로 실행) :

2642565
time: 23.785543s
2642565
time: 0.000030s

출력 (Python 3.3.0, 2.7.2로 실행하면 매우 유사한 시간이 반환 됨) :

2642565
time: 14.426975s
2642565
time: 0.000021s

그리고 관련 질문, 일반적으로 PyPy가 훨씬 빠를 때 PyPy보다 Python 2 또는 3으로 실행할 때이 계산이 거의 두 배 빠른 이유는 무엇입니까?

답변:


164

모듈 식 지수 에 대한 Wikipedia 기사를 참조하십시오 . 기본적으로를 할 때 a**d % n실제로를 계산해야하는데 a**d이는 상당히 클 수 있습니다. 하지만 스스로 a**d % n계산하지 않고도 계산할 a**d수있는 방법 pow이 있습니다. **당신이 바로 계수를 취할려고하고 있다는 것을 알고 "미래를 볼"수 없기 때문에 작업자는이 작업을 수행 할 수 없습니다.


14
문서화 문자열이 무엇을 의미하는지 사실이다 +1>>> print pow.__doc__ pow(x, y[, z]) -> number With two arguments, equivalent to x**y. With three arguments, equivalent to (x**y) % z, but may be more efficient (e.g. for longs).
Hedde 반 된 Heide 데르

6
Python 버전에 따라 특정 조건에서만 해당 될 수 있습니다. IIRC, 3.x 및 2.7에서는 정수 유형 (및 음이 아닌 거듭 제곱)으로 만 3 인수 형식을 사용할 수 있으며 항상 기본 int유형으로 모듈 식 지수를 얻을 수 있지만 반드시 다른 정수 유형에서는 사용할 수 없습니다. 그러나 이전 버전에서는 C에 맞추는 것에 대한 규칙이 있었고 long3 인수 형식이 허용되었습니다 float. (2.1 이전 버전을 사용하지 않고 C 모듈의 사용자 정의 정수 유형을 사용하지 않기를 바랍니다. 이것은 당신에게 중요한의).
abarnert

13
귀하의 답변에 따르면 컴파일러가 표현식을보고 최적화하는 것은 불가능한 것처럼 보이지만 사실이 아닙니다. 단지 발생하는 전류 파이썬 컴파일러가 그것을 할 수 없다.
danielkza 2013 년

5
@danielkza : 사실입니다. 이론적으로 불가능하다는 것을 의미하지는 않았습니다. 아마도 "미래를 보지 않음"이 "미래를 보지 못함"보다 더 정확할 것입니다. 그러나 최적화는 일반적으로 매우 어렵거나 불가능할 수 있습니다. 들어 상수 피연산자는 최적화 된,하지만의 수 x ** y % n, x객체가 될 수있는 구현 __pow__하고, 임의의 수를 기반으로 구현하는 여러 다른 개체 중 하나를 반환 __mod__등도 임의의 숫자에 의존하는 방식으로,
BrenBarn

2
@danielkza : 또한 함수는 동일한 도메인을 가지고 있지 않습니다. .3 ** .4 % .5완벽하게 합법적이지만 컴파일러가이를 변환 pow(.3, .4, .5)하면 TypeError. 컴파일러가 알고 할 수있는 것 a, d그리고 n(어쩌면 그냥 특정 유형의 또는 필수 유형의 값이 보장되는 int변환이, 그렇지 않으면 도움이되지 않기 때문에,)와 d음이 아닌 것으로 보장된다. JIT가 할 수있는 일이지만, 동적 유형이 있고 추론이없는 언어의 정적 컴파일러는 할 수 없습니다.
abarnert

37

BrenBarn이 주요 질문에 대답했습니다. 제쳐두고 :

일반적으로 PyPy가 훨씬 빠르지 만 Python 2 또는 3으로 실행할 때 PyPy보다 거의 두 배 빠른 이유는 무엇입니까?

PyPy의 성능 페이지 를 읽으면 이것이 바로 PyPy가 잘하지 못하는 종류입니다. 실제로 그들이 제공하는 첫 번째 예입니다.

나쁜 예에는 최적화 할 수없는 지원 코드에 의해 수행되는 긴 long으로 계산하는 것이 포함됩니다.

이론적으로는 거대한 지수화에 이어 mod를 모듈 식 지수화 (적어도 첫 번째 패스 이후)로 바꾸는 것은 JIT가 만들 수있는 변환이지만 PyPy의 JIT는 아닙니다.

참고로, 거대한 정수를 사용하여 계산을 수행해야하는 gmpy경우, 주류 사용 외부의 경우에 CPython의 기본 구현보다 훨씬 빠를 수있는, 같은 타사 모듈을 살펴보고 싶을 수 있습니다. 편리하지 않은 대신 직접 작성해야하는 추가 기능을 제공합니다.


2
롱 스가 해결되었습니다. pypy 2.0 베타 1을 사용해보십시오 (CPython보다 빠르지는 않지만 느려서는 안됩니다). gmpy는 MemoryError를 처리 할 방법이 없습니다. :(
fijal

@fijal : 예, gmpy또한 몇 가지 경우에 더 빠르지 않고 더 느리며, 많은 간단한 일을 덜 편리하게 만듭니다. 그것은 아니다 항상 대답-하지만 때로는이다. 따라서 거대한 정수를 다루고 있고 Python의 기본 유형이 충분히 빠르지 않은지 살펴볼 가치가 있습니다.
abarnert 2013 년

1
그리고 만약 당신의 숫자가 크
더라도

1
PyPy가 오랫동안 GMP 라이브러리를 사용하지 않게 만든 요인입니다. 여러분에게는 괜찮을 수 있지만 Python VM 개발자에게는 좋지 않습니다. malloc은 많은 RAM을 사용하지 않고 실패 할 수 있습니다. 여기에 아주 많은 수를 넣으면됩니다. 그 시점부터 GMP의 동작은 정의되지 않았으며 Python은이를 허용 할 수 없습니다.
fijal

1
@fijal : Python의 내장 유형을 구현하는 데 사용해서는 안된다는 데 전적으로 동의합니다. 그렇다고 어떤 용도로도 사용해서는 안된다는 의미는 아닙니다.
abarnert

11

모듈 식 지수화를 수행하는 지름길이 있습니다. 예를 들어 a**(2i) mod n모든 ifrom 1to 에서 필요한 중간 결과를 log(d)함께 곱 (mod n) 할 수 있습니다. 3-argument와 같은 전용 모듈 식 지수 함수 pow()는 모듈 식 산술을 수행하고 있음을 알고 있기 때문에 이러한 트릭을 활용할 수 있습니다. 파이썬 파서는 베어 표현식이 주어지면이를 인식 할 수 없으므로 a**d % n전체 계산을 수행합니다 (훨씬 더 오래 걸립니다).


3

방법 x = a**d % n계산 올리이다 a받는 d전력, 다음으로 모듈로한다는 n. 첫째, a이 크면 큰 수를 생성 한 다음 잘립니다. 그러나 x = pow(a, d, n)는 마지막 n숫자 만 추적 되도록 최적화 될 가능성이 가장 높으며 , 이는 곱셈 모듈로 숫자를 계산하는 데 필요한 모든 것입니다.


6
"x ** d를 계산하려면 d 곱셈이 필요합니다"-올바르지 않습니다. O (log d) (매우 넓은) 곱셈으로 할 수 있습니다. 제곱에 의한 지수는 모듈없이 사용할 수 있습니다. 곱셈의 순전히 크기가 여기서 선두를 차지합니다.
John Dvorak 2013 년

내가 파이썬은 동일한 지수 알고리즘을 이용하지 것이라고 생각하는 이유 @JanDvorak 사실은 잘 모르겠어요 **경우와 pow.
Yuushi

5
마지막 "n"자리가 아니라 계산을 Z / nZ로 유지합니다.
토마스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.