<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          pypy真的能讓python比c還快?

          共 13066字,需瀏覽 27分鐘

           ·

          2021-05-21 01:04

          點(diǎn)擊上方“Python爬蟲與數(shù)據(jù)挖掘”,進(jìn)行關(guān)注

          回復(fù)“書籍”即可獲贈(zèng)Python從入門到進(jìn)階共10本電子書

          仰天大笑出門去,我輩豈是蓬蒿人。

          最近 “pypy為什么能讓python比c還快” 刷屏了,原文講的內(nèi)容偏理論,干貨比較少。我們可以再深入一點(diǎn)點(diǎn),了解pypy的真相。

          正式開始之前,多嘮叨兩句。我司發(fā)力多個(gè)賽道的游戲,其中包括某魚類游戲Top2項(xiàng)目,拿過阿拉丁神燈獎(jiǎng)的SLG卡牌小游戲項(xiàng)目和海外三消游戲。這些不同類型的游戲,后端大多是使用的是pypy。對(duì)于如何使用pypy,我有一點(diǎn)使用經(jīng)驗(yàn)可以聊聊。話不多說,正式開始,本文包括下面幾個(gè)部分:

          • 語言分類
          • python的解釋器實(shí)現(xiàn)
          • pypy為什么快
          • 性能比較
          • 性能優(yōu)化方法
          • pypy的特性
          • 小結(jié)

          語言分類

          我們先從最基本的一些語言分類概念聊起,對(duì)這部分內(nèi)容非常了解的朋友可以跳過。

          靜態(tài)語言 vs 動(dòng)態(tài)語言

          如果在編譯時(shí)知道變量的類型,則該語言為靜態(tài)類型。靜態(tài)類型語言的常見示例包括Java,C,C ++,F(xiàn)ORTRAN,Pascal和Scala。在靜態(tài)類型語言中,一旦使用類型聲明了變量,就無法將其分配給其他不同類型的變量,這樣做會(huì)在編譯時(shí)引發(fā)類型錯(cuò)誤。

          # java

          int data;
          data = 50;
          data = “Hello Game_404!”; // causes an compilation error

          如果在運(yùn)行時(shí)檢查變量的類型,則語言是動(dòng)態(tài)類型的。動(dòng)態(tài)類型語言的常見示例包括JavaScript,Objective-C,PHP,Python,Ruby,Lisp和Tcl。在動(dòng)態(tài)類型語言中,變量在運(yùn)行時(shí)通過賦值語句綁定到對(duì)象,并且可以在程序執(zhí)行期間將相同的變量綁定到不同類型的對(duì)象。

          # python

          data = 10;
          data = "Hello Game_404!"; // no error caused
          data = data + str(10)

          一般來說靜態(tài)語言編譯成字節(jié)碼執(zhí)行,動(dòng)態(tài)語言使用解釋器執(zhí)行。編譯型語言性能更高,但是較難移植到不同的CPU架構(gòu)體系和操作系統(tǒng)。解釋型語言易于移植,性能會(huì)比編譯語言要差得多。這是頻譜的兩個(gè)極端。

          強(qiáng)類型語言 vs 弱類型語言

          強(qiáng)類型語言是一種變量被綁定到特定數(shù)據(jù)類型的語言,如果類型與表達(dá)式中的預(yù)期不一致,將導(dǎo)致類型錯(cuò)誤,比如下面這個(gè):

          # python

          temp = “Hello Game_404!”
          temp = temp + 10; // program terminates with below stated error (TypeError: must be str, not int)

          python和我們感覺不一致,背叛了弱類型語言,不像世界上最好的語言:(

          # php

          $temp = “Hello Game_404!”;
          $temp = $temp + 10; // no error caused
          echo $temp;

          常見編程語言的象限分類如下圖:

          language

          這一部分內(nèi)容主要翻譯自參考鏈接1

          python的解釋器實(shí)現(xiàn)

          python是一門動(dòng)態(tài)編程語言,由特定的解釋器解釋執(zhí)行。下面是一些解釋器實(shí)現(xiàn):

          • CPython 使用c語言實(shí)現(xiàn)的解釋器
          • PyPy 使用python語言的子集RPython實(shí)現(xiàn)的解釋器,一般情況下PyPy比CPython快4.2倍
          • Stackless Python 帶有協(xié)程實(shí)現(xiàn)的解釋器
          • Jython Java實(shí)現(xiàn)的解釋器
          • IronPython .net實(shí)現(xiàn)的解釋器
          • Pyston 一個(gè)較新的實(shí)現(xiàn),是CPython 3.8.8的一個(gè)分支,具有其他針對(duì)性能的優(yōu)化。它針對(duì)大型現(xiàn)實(shí)應(yīng)用程序(例如Web服務(wù)),無需進(jìn)行開發(fā)工作即可提供高達(dá)30%的加速。
          • ...

          還有幾個(gè)相關(guān)概念:

          • IPython && Jupyter ipython是使用python構(gòu)建的交互式shell, Jupyter是其web化的包裝。
          • Anaconda 是一個(gè)python虛擬環(huán)境,Python數(shù)據(jù)科學(xué)常用。
          • mypyc 一個(gè)新的項(xiàng)目,將python編譯成c代碼庫(kù),以期提高python的運(yùn)行效率。
          • py文件和pyc文件 pyc文件是python編譯后的字節(jié)碼,也可以由python解釋器執(zhí)行。
          • wheel文件和egg文件 都是項(xiàng)目版本發(fā)布的打包文件,wheel是最新標(biāo)準(zhǔn)。
          • ...

          這里大家會(huì)有一個(gè)疑問,python不是解釋型語言嘛?怎么又有編譯后的pyc。是這樣的: py文件編譯成pyc后,解釋器默認(rèn) 優(yōu)先 執(zhí)行pyc文件,這樣可以加快python程序的 啟動(dòng)速度 (注意是啟動(dòng)速度)。繼背叛弱類型語言后,python這個(gè)鬼又在編譯語言和解釋語言之間橫跳。

          還有一個(gè)事件是Go語言在1.5版本實(shí)現(xiàn)自舉。Go語言在1.5版本之前使用c實(shí)現(xiàn)的編譯器,在1.5版本時(shí)候使用Go實(shí)現(xiàn)了自己的編譯器,這里有一個(gè)雞生蛋和蛋生雞的過程,也挺有意思。

          pypy為什么快

          pypy使用python的子集rpython實(shí)現(xiàn)了解釋器,和前面介紹的Go的自舉有點(diǎn)類似。反常識(shí)的是rpython的解釋器會(huì)比c實(shí)現(xiàn)的解釋器快?主要是因?yàn)閜ypy使用了JIT技術(shù)。

          Just-In-Time (JIT) Compiler 試圖通過對(duì)機(jī)器碼進(jìn)行一些實(shí)際的編譯和一些解釋來獲得兩全其美的方法。簡(jiǎn)而言之,以下是JIT編譯為提高性能而采取的步驟:

          1. 標(biāo)識(shí)代碼中最常用的組件,例如循環(huán)中的函數(shù)。
          2. 在運(yùn)行時(shí)將這些零件轉(zhuǎn)換為機(jī)器碼。
          3. 優(yōu)化生成的機(jī)器碼。
          4. 用優(yōu)化的機(jī)器碼版本交換以前的實(shí)現(xiàn)。

          這也是 “pypy為什么能讓python比c還快” 一文中的示例展現(xiàn)出來的能力。pypy除了速度快外,還有下面一些特點(diǎn):

          • 內(nèi)存使用情況比cpython少
          • gc策略更優(yōu)化
          • Stackless 協(xié)程模式默認(rèn)支持,支持高并發(fā)
          • 兼容性好,高度兼容cpython實(shí)現(xiàn),基本可以無縫切換

          以上都是宣稱

          pypy這么強(qiáng),快和省都占了,為什么沒有大規(guī)模流行起來呢? 我個(gè)人認(rèn)為,主要還是python的原因。

          1. python生態(tài)中大量庫(kù)采用c實(shí)現(xiàn),特別是科學(xué)計(jì)算/AI相關(guān)的庫(kù),pypy在這塊并沒有優(yōu)勢(shì)。pypy快的主要在pure-python,也就是純粹的python實(shí)現(xiàn)部分。
          2. pypy適合長(zhǎng)駐內(nèi)存的高并發(fā)應(yīng)用(web服務(wù)類)
          3. python是一門膠水語言,并不追求性能極致,即使快4倍也不夠快:( ????隙ū炔簧蟘,原文中的c應(yīng)該是 偷換了概念 ,指c實(shí)現(xiàn)的cpython解釋器。

          需要注意的是,pypy一樣也有GIL的存在, 所以高并發(fā)主要在stackless。

          這一部分內(nèi)容參考自參考鏈接2

          性能比較

          我們可以編寫性能測(cè)試用例,用代碼說話,對(duì)各個(gè)實(shí)現(xiàn)進(jìn)行對(duì)比。本文的測(cè)試用例并不嚴(yán)謹(jǐn),不過也足夠說明一些問題了。

          開車和步行

          原文中累加測(cè)試用例是100000000次,我們減少成1000次:

          import time

          start = time.time()
          number = 0
          for i in range(1000):
              number += i

          print(number)
          print(f"Elapsed time: {time.time() - start} s")

          測(cè)試結(jié)果如下表(測(cè)試環(huán)境在本文附錄部分):

          解釋器循環(huán)次數(shù)耗時(shí)(s)
          python310000.00014281272888183594
          pypy310000.00036716461181640625

          結(jié)果顯示運(yùn)行1000次循環(huán)的情況下cpython要比pypy快,這和循環(huán)100000000次 相反 。用下面的例子可以非常形象的解釋這一點(diǎn)。

          假設(shè)您想去一家離您家很近的商店。您可以步行或開車。您的汽車顯然比腳快得多。但是,請(qǐng)考慮需要執(zhí)行以下操作:

          1. 去你的車庫(kù)。
          2. 啟動(dòng)你的車。
          3. 讓汽車暖一點(diǎn)。
          4. 開車去商店。
          5. 查找停車位。
          6. 在返回途中重復(fù)該過程。

          開車要涉及很多開銷,如果您想去的地方在附近,這并不總是值得的!現(xiàn)在想想如果您想去五十英里外的鄰近城市會(huì)發(fā)生什么。開車去那里而不是步行肯定是值得的。

          舉例來自參考鏈接2

          盡管速度的差異并不像上面類比那么明顯,但是PyPy和CPython的情況也是如此。

          橫向?qū)Ρ?span style="display: none;">

          我們橫向?qū)Ρ纫幌耤,python3, pypy3, js 和lua的性能。

          # js
          const start = Date.now();
          let number = 0
          for (i=0;i<100000000;i++){
           number += i
          }
          console.log(number)
          const millis = Date.now() - start;
          console.log(`milliseconds elapsed = `, millis);

          # lua
          local starttime = os.clock();
          local number = 0
          local total = 100000000-1
          for i=total,1,-1 do
              number = number+i
          end
          print(number)
          local endtime = os.clock();
          print(string.format("elapsed time  : %.4f", endtime - starttime));

          # c
          #include <stdio.h>
          #include <time.h>

          const long long TOTAL = 100000000;

          long long mySum()
          {
              long long number=0;
              long long i;
              for( i = 0; i < TOTAL; i++ )
              {
                  number += i;
              }
              return number;
          }

          int main(void)
          {
              // Start measuring time
              clock_t start = clock();

              printf("%llu \n", mySum());
              // Stop measuring time and calculate the elapsed time
              clock_t end = clock();
              double elapsed = (end - start)/CLOCKS_PER_SEC;
              printf("Time measured: %.3f seconds.\n", elapsed);
              return 0;
          }
          解釋器循環(huán)次數(shù)耗時(shí)(s)
          c1000000000.000
          pypy31000000000.15746307373046875
          js1000000000.198
          lua1000000000.8023
          python310000000010.14592313766479

          測(cè)試結(jié)果可見,c無疑是最快的,秒殺其它語言,這是編譯語言的特點(diǎn)。在解釋語言中,pypy3表現(xiàn)配得上優(yōu)秀二字。

          內(nèi)存占用

          測(cè)試用例中增加內(nèi)存占用的輸出:

          p = psutil.Process()
          mem = p.memory_info()
          print(mem)

          測(cè)試結(jié)果如下:

          # python3
          pmem(rss= 9027584, vms=4747534336, pfaults= 2914, pageins=1)

          # pypy3
          pmem(rss=39518208, vms=5127745536, pfaults=12188, pageins=58)

          pypy3的內(nèi)存占用會(huì)比python3要高,這個(gè)才科學(xué),用內(nèi)存空間換了運(yùn)行時(shí)間。當(dāng)然這個(gè)評(píng)測(cè)并不嚴(yán)謹(jǐn),實(shí)際情況如何,pypy宣稱的內(nèi)存占用較少,我表示懷疑,但是沒有證據(jù)。

          性能優(yōu)化方法

          了解語言的性能比較后,我們?cè)倏纯匆恍┬阅軆?yōu)化的方法,這對(duì)在cpython和pypy之間選型有幫助。

          使用c函數(shù)

          python中使用c函數(shù),比如這里的累加可以使用reduce替換,可以提高效率:

          def my_add(a, b):
              return a + b

          number = reduce(add, range(100000000))
          解釋器次數(shù)耗時(shí)(s)
          pypy3reduce0.08371400833129883
          pypy31000000000.15746307373046875
          python3reduce5.705173015594482 s
          python3100000000循環(huán)10.14592313766479

          結(jié)果展示,reduce對(duì)cpython和pypy都有效。

          優(yōu)化循環(huán)

          優(yōu)化最關(guān)鍵的地方,提高算法效率,減少循環(huán)。更改一下累加的需求,假設(shè)我們是求100000000以內(nèi)的偶數(shù)的和,下面展示了使用range的步進(jìn)減少循環(huán)次數(shù)來提高性能:

          try:
              xrange  # python2注意使用xrange是迭代器,而range是返回一個(gè)list
          except NameError:  # python3
              xrange = range

          def test_0():
              number = 0
              for i in range(100000000):
                  if i % 2 == 0:
                      number += i
              return number

          def test_1():
              number = 0

              for i in xrange(0, 100000000, 2):
                  number += i
              return number
          解釋器循環(huán)次數(shù)耗時(shí)(s)
          python3500000002.6723649501800537 s
          python31000000006.530670881271362 s

          循環(huán)次數(shù)減半后,有效率顯著提升。

          靜態(tài)類型

          python3可以使用類型注解,提高代碼可讀性。類型確定邏輯上對(duì)性能有幫助,每次處理數(shù)據(jù)的時(shí)候,不用再進(jìn)行類型推斷。

          number: int = 0
          for i in range(100000000):
              number += i
          解釋器循環(huán)次數(shù)類型耗時(shí)(s)
          python3100000000int9.492593050003052 s
          python3100000000不定義10.14592313766479 s

          內(nèi)存相當(dāng)于一個(gè)空間,我們要用不同的盒子去填充它。圖中左邊部分1,都使用長(zhǎng)度為4(想像float類型)的盒子填充,一行一個(gè),速度最快;圖中中間部分2,使用長(zhǎng)度為3(想像long類型)和長(zhǎng)度為1(想像int類型)的箱子,一行2個(gè),也挺快;圖中右側(cè)3,雖然箱子長(zhǎng)度仍然是3和1,但是由于沒有刻度,填充時(shí)候需要試裝,所以速度最慢。

          數(shù)據(jù)類型

          算法的魅力

          優(yōu)化到最后,最重量級(jí)的內(nèi)容登場(chǎng):高斯求和算法。高斯的故事,想必大家都不陌生,下面是算法實(shí)現(xiàn):

          def gaussian_sum(total: int) -> int:
              if total & 1 == 0:
                  return (1 + total) * int(total / 2)
              else:
                  return total * int((total - 1) / 2) + total


          # 4999999950000000
          number = gaussian_sum(100000000 - 1)
          解釋器循環(huán)次數(shù)耗時(shí)(s)
          python3高斯求和4.100799560546875e-05 s
          python3100000000循環(huán)10.14592313766479

          使用高斯求和后,程序秒開。這大概就是業(yè)內(nèi)面試,要考算法的真相,也是算法的魅力所在。

          優(yōu)化的原則

          簡(jiǎn)單介紹一下優(yōu)化的原則,主要是下面2點(diǎn):

          1. 使用測(cè)試而不是推測(cè)。
          python3 -m timeit 'x=3' 'x%2'                                  
          10000000 loops, best of 5: 25.3 nsec per loop
          python3 -m timeit 'x=3' 'x&1'
          5000000 loops, best of 5: 41.3 nsec per loop
          python2 -m timeit 'x=3' 'x&1'
          10000000 loops, best of 3: 0.0262 usec per loop
          python2 -m timeit 'x=3' 'x%2'
          10000000 loops, best of 3: 0.0371 usec per loop

          上面示例展示了,求奇偶的情況下,python3中位運(yùn)算比取模慢,這是個(gè)反直覺推測(cè)的地方。在我的python冷兵器合集一文中也有介紹。而且需要注意的是,python2和python3表現(xiàn)相反,所以性能優(yōu)化要實(shí)測(cè),注意環(huán)境和實(shí)效性。

          1. 遵循2/8法則, 不要過度優(yōu)化,不用贅述。

          pypy的特性

          pypy還有下面一些特性:

          • cffi pypy推薦使用cffi的方式加載c
          • cProfile pypy下使用cProfile檢測(cè)性能無效
          • sys.getsizeof pypy的gc方式差異,sys.getsizeof無法使用
          • __slots__ cpython使用的slots,在pypy下失效

          使用slots在python對(duì)象中,可以減少對(duì)象內(nèi)存占用,提高效率,下面是測(cè)試用例:

          def test_0():
              class Player(object):

                  def __init__(self, name, age):
                      self.name = name
                      self.age = age

              players = []
              for i in range(10000):
                  p = Player(name="p" + str(i), age=i)
                  players.append(p)
              return players


          def test_1():
              class Player(object):
                  __slots__ = "name""age"

                  def __init__(self, name, age):
                      self.name = name
                      self.age = age

              players = []
              for i in range(10000):
                  p = Player(name="p" + str(i), age=i)
                  players.append(p)
              return players

          測(cè)試日志如下:

          # python3 slots
          pmem(rss=10776576, vms=5178499072, pfaults=3351, pageins=58)
          Elapsed time:  0.010818958282470703 s

          # python3 默認(rèn)
          pmem(rss=11792384, vms=5033795584, pfaults=3587, pageins=0)
          Elapsed time:  0.01322031021118164 s

          # pypy3 slots
          pmem(rss=40042496, vms=5263011840, pfaults=12341, pageins=4071)
          Elapsed time:  0.005321025848388672 s

          # pypy3 默認(rèn)
          pmem(rss=39862272, vms=4974653440, pfaults=12280, pageins=0)
          Elapsed time:  0.004619121551513672 s

          詳細(xì)信息可以看參考鏈接4和5

          pypy最重要的特性還是stackless,支持高并發(fā)。這里有IO密集型任務(wù)(I/O-bound)和CPU密集型任務(wù)(compute-bound)的區(qū)分,CPU密集型任務(wù)的代碼,速度很慢,是因?yàn)閳?zhí)行大量CPU指令,比如上文的for循環(huán);I / O密集型,速度因磁盤或網(wǎng)絡(luò)延遲而變慢,這兩者之間是有區(qū)別的。這部分內(nèi)容,要介紹清楚也不容易,容我們下章見。

          小結(jié)

          python是一門解釋型編程語言,具有多種解釋器實(shí)現(xiàn),常見的是cpython的實(shí)現(xiàn)。pypy使用了JIT技術(shù),在一些常見的場(chǎng)景下可以顯著提高python的執(zhí)行效率,對(duì)cpython的兼容性也很高。如果項(xiàng)目純python部分較多,推薦嘗試使用pypy運(yùn)行程序。

          注:由于個(gè)人能力有限,文中示例如有謬誤,還望海涵。

          附錄

          測(cè)試環(huán)境

          • MacBook Pro (16-inch, 2019)(2.6 GHz 六核Intel Core i7)
          • Python 2.7.16
          • Python 3.8.5
          • Python 3.6.9 [PyPy 7.3.1 with GCC 4.2.1 Compatible Apple LLVM 11.0.3 (clang-1103.0.32.59)]
          • Python 2.7.13 [PyPy 7.3.1 with GCC 4.2.1 Compatible Apple LLVM 11.0.3 (clang-1103.0.32.59)]
          • lua#Lua 5.2.3  Copyright (C) 1994-2013 Lua.org, PUC-Rio
          • node#v10.16.3

          參考鏈接

          1. https://medium.com/android-news/magic-lies-here-statically-typed-vs-dynamically-typed-languages-d151c7f95e2b
          2. https://realpython.com/pypy-faster-python/
          3. https://www.pypy.org/index.html
          4. https://stackoverflow.com/questions/23068076/using-slots-under-pypy
          5. https://morepypy.blogspot.com/2010/11/efficiently-implementing-python-objects.html


          ------------------- End -------------------

          往期精彩文章推薦:

          歡迎大家點(diǎn)贊,留言,轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持

          想加入Python學(xué)習(xí)群請(qǐng)?jiān)诤笈_(tái)回復(fù)【入群

          萬水千山總是情,點(diǎn)個(gè)【在看】行不行

          /今日留言主題/

          隨便說一兩句吧~~

          瀏覽 81
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青娱乐亚洲视频 | 天天澡天天狠天天天做 | 噜噜视频 | 免费观看一级黄片 | 12321举报中心官网 |