Cython:使用C語言來加速Python

前言

Python語言易用,開發(fā)效率高筹煮,適用范圍廣遮精,這些優(yōu)點(diǎn)是我們經(jīng)常提起的,幾乎做到了家喻戶曉吧败潦。但Python語言的性能也一直是大多數(shù)使用Python和沒使用過Python的人一直詬病的本冲。為什么沒有使用過Python的人也詬病Python的性能呢,這就涉及到更深入的話題了变屁,本篇不做深入眼俊。和我這樣的能力不足的程序員不同是的是,一直有一些聰明人在享受這Python的便利的同時(shí)也沒有放棄從各個(gè)方向優(yōu)化Python的性能粟关。其中Cython就是一個(gè)常用的方案疮胖,Cython可以做到像C一樣的高性能环戈,同時(shí)兼顧Python的簡單易用。Cython通過一個(gè)Python/C API來完成兩個(gè)語言之間的交流澎灸,以提供在計(jì)算密集型任務(wù)上對(duì)Python的良好性能支持院塞,這里要注意的是只在計(jì)算密集型任務(wù)上適應(yīng)這樣的解決方案,大部分應(yīng)用不需要這么做性昭,反而會(huì)適得其反拦止。

原生Python

上節(jié)說到只有那些計(jì)算密集型任務(wù)才考慮做性能優(yōu)化,沒有充分的理由不要過早做性能優(yōu)化糜颠,要知道過早的優(yōu)化是萬惡之源汹族。所以我們的例子也是舉一個(gè)計(jì)算密集型示例。計(jì)算一定范圍內(nèi)那些數(shù)字是素?cái)?shù)是一個(gè)典型的計(jì)算密集型任務(wù)其兴。我們這次就用這個(gè)作為示例做演示顶瞒。首先是Python語言實(shí)現(xiàn)部分:

from math import sqrt


def primes(n):
    results = [1,]
    for i in range(2, n):
        for j in range(2, int(sqrt(i))):
            if i % j == 0:
                break
        else:
            results.append(i)
    return results


def main():
    primes(3000000)

if __name__ == '__main__':
    main()

我對(duì)這段代碼做個(gè)簡單的解釋,代碼分為三個(gè)部分:第一段是功能的主題元旬,負(fù)責(zé)完成主要功能榴徐;第二段是主函數(shù)定義,就只是一個(gè)對(duì)primes的調(diào)用而已匀归;第三段是實(shí)現(xiàn)運(yùn)行當(dāng)前文件的時(shí)候自動(dòng)調(diào)用main函數(shù)坑资。所有的功能都在primes函數(shù)里面,這個(gè)函數(shù)在一個(gè)數(shù)字n的范圍內(nèi)穆端,求出所有這個(gè)范圍內(nèi)的素?cái)?shù)的列表袱贮。1是公認(rèn)的素?cái)?shù),所以我們直接加入到列表中体啰。以下數(shù)字從2開始直到n字柠,我們給所有的數(shù)字i挨個(gè)除以2直到它的平方根。如果都不能整除就跳出循環(huán)并把結(jié)果加入到素?cái)?shù)列表中狡赐。這稍微解釋下為社么不是算到i而是算到i的平方根窑业,因?yàn)槿绻钡絠的平方根都不能整除的話,那后面的數(shù)字也都不能整除枕屉,所以不必要都驗(yàn)證常柄,這樣能在算法上節(jié)約很多計(jì)算資源,使計(jì)算過程快很多搀擂。

以上代碼是計(jì)算300萬以內(nèi)的素?cái)?shù)列表西潘,我們先使用Python原生方式來執(zhí)行看一下需要多少時(shí)間。

time python primes.py

real    0m12.171s
user    0m11.828s
sys     0m0.344s

大概需要12秒多的時(shí)間哨颂,我們下一步要直接使用Cython來運(yùn)行一下這個(gè)Python代碼喷市,看看能快多少?

直接使用Cython來運(yùn)行Python

在上面的Python原生代碼執(zhí)行中威恼,我們已經(jīng)得到了一個(gè)時(shí)間品姓,在這一步我們要直接使用Cython直接運(yùn)行不加修改的Python代碼看看能提高多少寝并。在這一步之前我們要先安裝Cython:

pip install cython

使用Cython的步驟大概是三步:

  1. 把primes.py改為primes.pyx
from math import sqrt


def primes(n):
  results = [1,]
  for i in range(2, n):
      for j in range(2, int(sqrt(i))):
          if i % j == 0:
              break
      else:
          results.append(i)
  return results


def main():
  primes(3000000)

if __name__ == '__main__':
  main()
  1. 增加一個(gè)setup.py文件
from distutils.core import setup
from Cython.Build import cythonize

setup(
  ext_modules = cythonize('primes.pyx')
)
  1. 把Python編譯為二進(jìn)制代碼,
python setup.py build_ext --inplace

這一步會(huì)產(chǎn)生一些c源文件和編譯產(chǎn)生的動(dòng)態(tài)鏈接庫文件腹备。

ls
build  primes.c  primes.cpython-38-x86_64-linux-gnu.so  primes.py  primes.pyx  setup.py

具體步驟會(huì)在后面解釋的衬潦。

  1. 運(yùn)行
time python -c "import primes; primes.main()"
real    0m7.501s
user    0m7.281s
sys     0m0.234s

在以上步驟中,有三個(gè)重要的過程

  1. 把pyx文件編譯成調(diào)用了Python源碼的C/C++代碼primes.c
  2. 把C代碼編譯成動(dòng)態(tài)鏈接庫primes.cpython-38-x86_64-linux-gnu.so
  3. 使用Python直接調(diào)用動(dòng)態(tài)鏈接庫植酥。

由以上的步驟的執(zhí)行結(jié)果來看镀岛,并沒有提高太多,只大概提交了一倍的速度友驮,這是因?yàn)镻ython的運(yùn)行速度慢除了因?yàn)槭墙忉寛?zhí)行以外還有一個(gè)最重要的原因是Python是動(dòng)態(tài)類型語言漂羊,每個(gè)變量在運(yùn)行前是不直到類型是什么的,所以即便編譯為二進(jìn)制代碼同樣速度不會(huì)太快卸留,這時(shí)候我們需要深度使用Cython來給Python提速了拨与,就是使用Cython來指定Python的數(shù)據(jù)類型。

使用Cython改進(jìn)的靜態(tài)類型指定

這一步不同就是primes.pyx文件和以上的文件不同艾猜,我們?cè)谄渲屑尤腩愋椭付ǖ拇a:

from math import sqrt


def primes(int n):
    cdef int i, j
    results = [1,]
    for i in range(2, n):
        for j in range(2, int(sqrt(i))):
            if i % j == 0:
                break
        else:
            results.append(i)
    return results


def main():
    primes(3000000)

if __name__ == '__main__':
    main()

其中的代碼只有兩處不同:

  1. 函數(shù)參數(shù)的類型指定:int n
  2. 函數(shù)中使用最頻繁的兩個(gè)變量的類型指定:cdef int i, j
    再運(yùn)行以上的相同步驟得到這次的運(yùn)行結(jié)果:
time python -c "import primes; primes.main()"

real    0m0.799s
user    0m0.734s
sys     0m0.063s

速度大概是原生Python的15倍左右,這只是在把Python代碼中常用的幾個(gè)變量改為靜態(tài)類型的情況下捻悯,如果把更多的變量和函數(shù)的返回值等都改為Cython的靜態(tài)類型后匆赃,性能一般能提升到原來的20-30倍。

后記

Python是一個(gè)很好用的語言今缚,效率問題大多數(shù)情況下可以通過橫向增加計(jì)算資源或者其他方式來彌補(bǔ)效率的不足算柳。極少數(shù)情況下是需要使用一些手段來提高語言的運(yùn)行效率。除了以上介紹的cython以外還有其他方案:pypy和Shed Skin等解決方案姓言,在以后的文檔中會(huì)分享其他的解決方案瞬项。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市何荚,隨后出現(xiàn)的幾起案子囱淋,更是在濱河造成了極大的恐慌,老刑警劉巖餐塘,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妥衣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡戒傻,警方通過查閱死者的電腦和手機(jī)税手,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來需纳,“玉大人芦倒,你說我怎么就攤上這事〔霍妫” “怎么了兵扬?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵麻裳,是天一觀的道長。 經(jīng)常有香客問我周霉,道長掂器,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任俱箱,我火速辦了婚禮国瓮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狞谱。我一直安慰自己乃摹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布跟衅。 她就那樣靜靜地躺著孵睬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伶跷。 梳的紋絲不亂的頭發(fā)上掰读,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音叭莫,去河邊找鬼蹈集。 笑死,一個(gè)胖子當(dāng)著我的面吹牛雇初,可吹牛的內(nèi)容都是我干的拢肆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼靖诗,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼郭怪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刊橘,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤鄙才,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后促绵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咒循,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年绞愚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叙甸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡位衩,死狀恐怖裆蒸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糖驴,我是刑警寧澤僚祷,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布佛致,位于F島的核電站,受9級(jí)特大地震影響辙谜,放射性物質(zhì)發(fā)生泄漏俺榆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一装哆、第九天 我趴在偏房一處隱蔽的房頂上張望罐脊。 院中可真熱鬧,春花似錦蜕琴、人聲如沸萍桌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽上炎。三九已至,卻和暖如春雏搂,著一層夾襖步出監(jiān)牢的瞬間藕施,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工凸郑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裳食,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓线椰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親尘盼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子憨愉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355