Pow (a, d, n) ** d% n की तुलना में इतना तेज क्यों है?


110

मैं मिलर-राबिन प्राइमलिटी टेस्ट को लागू करने की कोशिश कर रहा था , और यह हैरान था कि यह संख्या (~ 7 अंक) के लिए इतना लंबा (> 20 सेकंड) क्यों ले रही थी। मुझे अंततः समस्या का स्रोत होने के लिए कोड की निम्न पंक्ति मिली:

x = a**d % n

(जहां a, dऔर, nसभी समान हैं, लेकिन असमान हैं, संख्याओं को midsize करते हैं, **प्रतिपादक ऑपरेटर है, और %मॉड्युलर ऑपरेटर है)

मैंने तब इसे निम्नलिखित के साथ बदलने की कोशिश की:

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

आउटपुट (पायथन 3.3.0 के साथ चलाएं, 2.7.2 बहुत बार समान रिटर्न):

2642565
time: 14.426975s
2642565
time: 0.000021s

और एक संबंधित प्रश्न, यह गणना PyPy के साथ पायथन 2 या 3 के साथ चलने पर लगभग दोगुनी तेजी से क्यों होती है, जब आमतौर पर PyPy बहुत तेज होता है ?

जवाबों:


164

मॉड्यूलर घातांक पर विकिपीडिया लेख देखें । असल में, जब आप करते हैं 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).
डॉकस्ट्रिंग का

6
आपके पायथन संस्करण के आधार पर, यह केवल कुछ शर्तों के तहत सही हो सकता है। IIRC, 3.x और 2.7 में, आप केवल अभिन्न प्रकार (और गैर-नकारात्मक शक्ति) के साथ तीन-तर्क फॉर्म का उपयोग कर सकते हैं, और आपको हमेशा मूल intप्रकार के साथ मॉड्यूलर घातांक मिलेगा , लेकिन जरूरी नहीं कि अन्य अभिन्न प्रकारों के साथ। लेकिन पुराने संस्करणों में सी में फिटिंग के बारे में नियम थे long, तीन-तर्क फॉर्म के लिए अनुमति दी गई थी float, आदि (उम्मीद है कि आप 2.1 या उससे पहले का उपयोग नहीं कर रहे हैं, और सी मॉड्यूल से किसी भी कस्टम अभिन्न प्रकार का उपयोग नहीं कर रहे हैं, इसलिए कोई नहीं आप के लिए यह मायने रखता है।)
abarnert

13
आपके उत्तर से ऐसा लगता है कि संकलक के लिए अभिव्यक्ति देखना और उसे अनुकूलित करना असंभव है, जो कि सच नहीं है। यह सिर्फ ऐसा होता है कि कोई भी मौजूदा पायथन कंपाइलर ऐसा नहीं करता है।
danielkza

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

ब्रेनबार ने आपके मुख्य प्रश्न का उत्तर दिया। अपनी तरफ के लिए:

जब यह PyPy की तुलना में पायथन 2 या 3 के साथ चलता है तो यह लगभग दोगुना तेज़ होता है, जब आमतौर पर PyPy बहुत तेज होता है?

यदि आप PyPy के प्रदर्शन पृष्ठ को पढ़ते हैं , तो यह ठीक उसी तरह का है जैसे PyPy अच्छा नहीं है - वास्तव में, पहला उदाहरण जो वे देते हैं:

खराब उदाहरणों में बड़े लोंगों के साथ संगणना करना शामिल है - जो कि अडॉप्टिबल सपोर्ट कोड द्वारा किया जाता है।

सैद्धांतिक रूप से, एक मॉड्युलर एक्सपेंशनशिप (कम से कम पहली पास के बाद) में एक मॉड के बाद एक विशाल एक्सपोनेंशन को बदलना एक परिवर्तन है जिसे एक JIT बना सकता है ... लेकिन PyPy का JIT नहीं।

एक साइड नोट के रूप में, यदि आपको विशाल पूर्णांक के साथ गणना करने की आवश्यकता है, तो आप तीसरे पक्ष के मॉड्यूल को देखना चाहते हैं gmpy, जो कभी-कभी मुख्यधारा के उपयोगों के बाहर कुछ मामलों में सीपीथॉन के मूल कार्यान्वयन की तुलना में बहुत तेज हो सकता है, और इसमें बहुत कुछ भी है अतिरिक्त कार्यक्षमता जो आपको कम सुविधाजनक होने की कीमत पर अन्यथा खुद को लिखना होगा।


2
लंबे समय से तय हो गया। pypy 2.0 बीटा 1 का प्रयास करें (यह CPython की तुलना में तेज़ नहीं होगा, लेकिन या तो धीमा नहीं होना चाहिए)। Gmpy के पास MemoryError को संभालने का कोई तरीका नहीं है :(
fijal

@ फिजूल: हाँ, और gmpyकुछ मामलों में तेजी के बजाय धीमी भी है, और बहुत सी सरल चीजों को कम सुविधाजनक बनाती है। यह हमेशा जवाब नहीं होता है - लेकिन कभी-कभी यह होता है। तो यह देखने लायक है कि क्या आप विशाल पूर्णांकों के साथ काम कर रहे हैं और पायथन का मूल प्रकार पर्याप्त तेजी से प्रतीत नहीं होता है।
दोपहर

1
और अगर आप परवाह नहीं करते हैं कि आपके नंबर बड़े होने के कारण आपका प्रोग्राम segfault करता है
fijal

1
यह वह कारक है जिसने PyPy को GMP लाइब्रेरी का उपयोग नहीं करने के लिए इसे लंबा किया है। यह आपके लिए ठीक हो सकता है, यह पायथन वीएम डेवलपर्स के लिए ठीक नहीं है। बहुत सारे रैम का उपयोग किए बिना मॉलोक विफल हो सकता है, बस वहां बहुत बड़ी संख्या में डाल दिया जाए। उस बिंदु से जीएमपी का व्यवहार अपरिभाषित है और पायथन इसकी अनुमति नहीं दे सकता है।
फिजल

1
@ फ़िजल: मैं पूरी तरह से सहमत हूं कि इसका इस्तेमाल पायथन के अंतर्निहित प्रकार को लागू करने के लिए नहीं किया जाना चाहिए। इसका मतलब यह नहीं है कि इसे कभी भी किसी भी चीज़ के लिए इस्तेमाल नहीं किया जाना चाहिए।
23

11

वहाँ मॉड्यूलर घातांक कर के लिए शॉर्टकट हैं: उदाहरण के लिए, आप पा सकते हैं a**(2i) mod nकि हर के लिए iसे 1करने के लिए log(d)और गुणा एक साथ (आधुनिक nमध्यवर्ती परिणाम आप की जरूरत)। 3-तर्क की तरह एक समर्पित मॉड्यूलर-एक्सपोनेंशिएशन फ़ंक्शन pow()इस तरह के ट्रिक्स का लाभ उठा सकता है क्योंकि यह जानता है कि आप मॉड्यूलर अंकगणित कर रहे हैं। पायथन पार्सर इसे नंगी अभिव्यक्ति को नहीं पहचान सकता है a**d % n, इसलिए यह पूरी गणना करेगा (जो अधिक समय लेगा)।


3

जिस तरह x = a**d % nसे गणना की जाती है aवह dशक्ति को बढ़ाने के लिए है , फिर इसके साथ मोडुलो n। सबसे पहले, यदि aबड़ा है, तो यह एक बड़ी संख्या बनाता है जो बाद में काट दिया जाता है। हालांकि, x = pow(a, d, n)सबसे अधिक संभावना है कि अनुकूलित किया जाता है ताकि केवल अंतिम nअंक को ट्रैक किया जा सके, जो कि एक संख्या को गुणा करने के लिए आवश्यक सभी हैं।


6
"यह x ** d की गणना करने के लिए d गुणन की आवश्यकता है" - सही नहीं है। आप इसे ओ (लॉग डी) (बहुत विस्तृत) गुणा में कर सकते हैं। स्क्वैरिंग द्वारा प्रतिपादक का उपयोग मॉड्यूल के बिना किया जा सकता है। गुणकों का सरासर आकार वही है जो यहाँ ले जाता है।
जॉन ड्वोरक

@JanDvorak यह सच है, मुझे यकीन है कि क्यों मैंने सोचा था कि अजगर के लिए एक ही घातांक एल्गोरिथ्म का उपयोग नहीं होगा नहीं कर रहा हूँ **के लिए के रूप में pow
युवाशी

5
अंतिम "एन" अंक नहीं .. यह सिर्फ जेड / एनजेड में गणना रखता है।
थॉमस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.