1. 程式人生 > >記錄一段生成素數python程式碼的調優過程 • cenalulu's Tech Blog

記錄一段生成素數python程式碼的調優過程 • cenalulu's Tech Blog

簡介:本文主要記錄了博主對一段使用python實現的素數生成程式碼的不斷優化過程。

背景:最近在刷Project Euler的題目,刷到第十題(計算2百萬以下素數的和)的時候發現之前的素數生成程式碼效率太低導致幾分鐘都出不來。於是通過不斷的調優,終於得到一個能在秒級算出2百萬以內的素數的generator。 本文的調優過程基本不涉及基於數論的調優,如果您希望得到一個擁有極致效能的python素數生成程式碼,可以使用pyprimes

第一版

素數也即:無法被除了1和本身以外的任何自然數整除的自然數。因此第一版的程式實現起來也格外的直白。把從2開始到數值本身-1範圍內的自然數都去除一下待判斷的數就能得到結論了。於是就有了第一版程式如下:

class Prime:
    def __init__(self):
        self.prime_list = []
        self.v = 3
    def get_prime(self):
	yield 2
        while True:
            for i in range(2, self.v):
                if self.v % i == 0:
                    break
            else:
                self.prime_list.append(self
.v) yield self.v self.v += 1

第二版

當然這段程式碼對於小的素數是可以work的。單當使用這段程式碼除錯Euler第三題的時候就會發現在判斷大數是否是素數時耗時很久。判斷一個數是否是素數的時間複雜度目前是O(N^2)。於是就想到需要通過減少判斷次數來達到提升程式碼效率的方法。最容易想到的一種方法是:大於被判斷值的平方根的數,無需再去判斷是否可以整除被判斷數了。現在單個素數的判斷時間複雜度優化到O(logN)

class Prime:
    def __init__(self):
        self
.prime_list = [2] self.v = 3 def get_prime(self): yield 2 while True: for i in (3, self.v): if self.v % i == 0: break elif i * i > self.v + 1: yield self.v self.prime_list.append(self.v) break else: self.prime_list.append(self.v) yield self.v self.v += 1

第三版

當然這還遠遠不夠,接踵而來的是第7題(找到第1000個素數)。尋找第N個素數的命題大大放大了我們時間複雜度。目前程式的時間複雜度是O(NlogN)。於是再次尋找是否還有其他可以跳過的檢查項。我們可以發現所有偶數其實是可以完全可以跳過不判斷的。但是判斷偶數本身也是一次計算操作,所以簡單的通過if self.v % 2 == 0 來跳過一次檢查並不能帶來很大的效能提升,所以這裡用了一個比較tricky方式跳過偶數:由於序列是從3開始檢查,因此把增長步進調整為2,那麼就自然跳過了所有偶數

class Prime:
    def __init__(self):
        self.prime_list = []
        self.v = 3
    def get_prime(self):
	yield 2
        while True:
            for i in self.prime_list:
                if self.v % i == 0:
                    break
            else:
                self.prime_list.append(self.v)
                yield self.v
            self.v += 2

這部分優化後,找到第N個素數的時間比原來少了一半。

第四版(最終版)

實際執行後發現即使時間相較之前少了一半,但總體執行時間仍然不理想(十幾秒級別)。冥想。。。過後又有一個大招:由於所有合數都能寫成素數相乘,因此如果能夠被某個合數整除,也必然能被某個素數整除。那麼我們的所有除數只需從被判斷的數小的素數集合中選擇即可。於是得到以下優化程式碼

class Prime:
    def __init__(self):
        self.prime_list = [2]
        self.v = 3
    def get_prime(self):
        yield 2
        while True:
            for i in self.prime_list:
                if self.v % i == 0:
                    break
                elif i * i > self.v + 1:
                    yield self.v
                    self.prime_list.append(self.v)
                    break
            else:
                self.prime_list.append(self.v)
                yield self.v
            self.v += 2

這部分優化後,找到第N個素數的時間從O(N/2logN)變成了O((logN)^2)