Pow (a, d, n) neden a ** d% n'den çok daha hızlı?


110

Miller-Rabin asallık testini uygulamaya çalışıyordum ve orta büyüklükteki sayılar (~ 7 basamak) için neden bu kadar uzun (> 20 saniye) sürdüğünü şaşırdım. Sonunda aşağıdaki kod satırını sorunun kaynağı olarak buldum:

x = a**d % n

(burada a, dve nhepsi benzer, ancak eşit olmayan, orta büyüklükteki sayılar **üs alma operatörü ve %modulo operatörüdür)

Daha sonra bunu aşağıdakilerle değiştirmeyi denedim:

x = pow(a, d, n)

ve kıyaslandığında neredeyse anlık.

Bağlam için, işte orijinal işlev:

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))

Örnek zamanlı bir hesaplama:

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)})

Çıktı (PyPy 1.9.0 ile çalıştırın):

2642565
time: 23.785543s
2642565
time: 0.000030s

Çıktı (Python 3.3.0 ile çalıştırın, 2.7.2 çok benzer zamanlar döndürür):

2642565
time: 14.426975s
2642565
time: 0.000021s

Ve ilgili bir soru, PyPy genellikle çok daha hızlıyken, neden bu hesaplama Python 2 veya 3 ile çalıştırıldığında PyPy'den neredeyse iki kat daha hızlıdır ?

Yanıtlar:


164

Modüler üs alma hakkındaki Wikipedia makalesine bakın . Temel olarak, yaptığınızda a**d % n, aslında hesaplamanız gerekir a**dki bu oldukça büyük olabilir. Ancak kendi kendini hesaplamak a**d % nzorunda kalmadan bilgi işlem yapmanın yolları vardır a**dve işte budur pow. **O hemen modülünü alacak olduğumuzu bilmenizi "geleceğe bakın" olamaz çünkü operatör bunu yapamaz.


14
+1 bu aslında dokümanın ima ettiği şeydir>>> 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 van der Heide

6
Python sürümünüze bağlı olarak, bu yalnızca belirli koşullar altında doğru olabilir. IIRC, 3.x ve 2.7'de, yalnızca üç bağımsız değişken formunu integral türleriyle (ve negatif olmayan kuvvetle) kullanabilirsiniz ve her zaman yerel türle modüler üs elde edersiniz int, ancak diğer integral türleriyle olması gerekmez. Ancak eski sürümlerde C'ye uydurmayla ilgili kurallar vardı long, üç argümanlı forma izin verildi float, vb. (Umarım 2.1 veya daha önceki bir sürümü kullanmıyorsunuz ve C modüllerinden herhangi bir özel integral türü kullanmıyorsunuz, yani hiçbiri bu sizin için önemli.)
abarnert

13
Cevabınıza göre bir derleyicinin ifadeyi görmesi ve optimize etmesi imkansız gibi görünüyor, bu doğru değil. Şöyleki hiçbir geçerli Python derleyici bunu söyledi.
danielkza

5
@danielkza: Bu doğru, teorik olarak imkansız olduğunu ima etmek istemedim. Belki "geleceğe bakmamak", "geleceği görememek" ten daha doğru olur. Bununla birlikte, optimizasyonun genel olarak son derece zor ve hatta imkansız olabileceğini unutmayın. İçin sürekli işlenen optimize fakat içinde olabilir x ** y % n, xbir nesne olabileceği uygular __pow__ve yine rastgele bir numara dayalı uygulayan birkaç farklı nesnelerden birini döndüren __mod__vb da rasgele sayı bağlıdır yollarla,
BrenBarn

2
@danielkza: Ayrıca, işlevler aynı etki alanına sahip değil: .3 ** .4 % .5tamamen yasaldır, ancak derleyici pow(.3, .4, .5)bunu buna dönüştürürse, bir TypeError. Derleyici olduğunu bilmek mümkün olurdu a, dve n(belki sadece spesifik tipi veya ayrılmaz bir tip değerleri olmasını garanti altına alınmıştır intdönüşüm aksi yardımcı olmuyor çünkü) ve dnegatif olmayan olması sağlanır. Bu, bir JIT'in düşünülebileceği bir şeydir, ancak dinamik türlere sahip bir dil için statik bir derleyici ve çıkarım yapamaz.
abarnert

37

BrenBarn ana sorunuzu yanıtladı. Kenara çekilmek için:

PyPy çok daha hızlıyken, neden Python 2 veya 3 ile çalıştırıldığında PyPy'den neredeyse iki kat daha hızlıdır?

PyPy'nin performans sayfasını okursanız , bu tam olarak PyPy'nin iyi olmadığı türden bir şeydir - aslında, verdikleri ilk örnek:

Kötü örnekler, optimize edilemeyen destek koduyla gerçekleştirilen büyük uzunlarla hesaplamalar yapmayı içerir.

Teorik olarak, büyük bir üs alma ve ardından bir modu modüler bir üslemeye dönüştürmek (en azından ilk geçişten sonra) bir JIT'in yapabileceği bir dönüşümdür… ama PyPy'nin JIT'i değil.

Bir yan not olarak, büyük tamsayılarla hesaplamalar yapmanız gerekiyorsa gmpy, ana akım kullanımların dışında bazı durumlarda CPython'un yerel uygulamasından çok daha hızlı olabilen ve aynı zamanda çok şey içeren üçüncü taraf modüllere bakmak isteyebilirsiniz. daha az kullanışlı olma pahasına kendi kendinize yazmak zorunda kalacağınız ek işlevler.


2
uzunlar düzeltildi. pypy 2.0 beta 1'i deneyin (CPython'dan daha hızlı olmayacak, ancak daha yavaş da olmamalıdır). gmpy'nin MemoryError'ı işlemek için bir yolu yok :(
fijal

@fijal: Evet, gmpyayrıca birkaç durumda daha hızlı yerine daha yavaş ve birçok basit şeyi daha kolay hale getiriyor. Her zaman cevap bu değil - ama bazen öyle. Bu nedenle, büyük tam sayılarla uğraşıyorsanız ve Python'un yerel türü yeterince hızlı görünmüyorsa, bakmaya değer.
abarnert

1
ve eğer sayılarınızın büyük olması sizin programınızı segfault yapar mı
umursamıyorsanız

1
PyPy'nin uzun süre GMP kitaplığını kullanmamasını sağlayan faktör budur. Sizin için uygun olabilir, Python VM geliştiricileri için uygun değildir. Malloc, çok fazla RAM kullanmadan başarısız olabilir, sadece oraya çok büyük bir sayı koyun. Bu noktadan itibaren GMP'nin davranışı tanımsızdır ve Python buna izin veremez.
fijal

1
@fijal: Python'un yerleşik türünü uygulamak için kullanılmaması gerektiğine tamamen katılıyorum. Bu, hiçbir zaman hiçbir şey için kullanılmaması gerektiği anlamına gelmez.
abarnert

11

Modüler üs yapmak için kısayollar vardır: Örneğin, bulabilirsiniz a**(2i) mod nher için igelen 1için log(d)çarpın birlikte (mod ve n) ihtiyacınız ara sonuçları. 3 bağımsız değişken gibi özel bir modüler üs alma işlevi pow()bu tür hilelerden yararlanabilir çünkü modüler aritmetik yaptığınızı bilir. Python ayrıştırıcısı, çıplak ifade verildiğinde bunu tanıyamaz a**d % n, bu nedenle tam hesaplamayı gerçekleştirecektir (bu çok daha uzun sürecektir).


3

Yolu x = a**d % nhesaplanır zam etmektir aiçin dgüç, daha sonra o modulo n. İlk olarak, eğer abüyükse, bu daha sonra kesilen çok büyük bir sayı oluşturur. Bununla birlikte, x = pow(a, d, n)büyük olasılıkla n, bir sayının çarpım modülünü hesaplamak için gerekli olan yalnızca son basamaklar izlenecek şekilde optimize edilmiştir .


6
"x ** d'yi hesaplamak için d çarpımı gerektirir" - doğru değil. Bunu O (log d) (çok geniş) çarpımlarında yapabilirsiniz. Kareleme yoluyla üs alma, modül olmadan kullanılabilir. Çarpanların tam boyutu, burada öncülük eden şeydir.
John Dvorak

Ben piton için aynı üs alma algoritması kullanmaz düşündüm neden @JanDvorak Doğru, emin değilim **gelince pow.
Yuushi

5
Son "n" basamak değil .. sadece hesaplamaları Z / nZ cinsinden tutar.
Thomas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.