python的numpy向量化語句為什么會比for快璧亚?

我們先來看看,python之類語言的for循環(huán)获询,和其它語言相比涨岁,額外付出了什么。

我們知道吉嚣,python是解釋執(zhí)行的。

舉例來說蹬铺,執(zhí)行 x = 1234+5678 尝哆,對編譯型語言,是從內(nèi)存讀入兩個short int到寄存器甜攀,然后讀入加法指令秋泄,通知CPU內(nèi)部的加法器動作,最后把加法器輸出存儲到x對應(yīng)的內(nèi)存單元(實質(zhì)上规阀,最后這個動作幾乎總會被自動優(yōu)化為“把加法器輸出暫存到寄存器而不是內(nèi)存單元恒序,因為訪問內(nèi)存的時間消耗常常是訪問寄存器的幾十倍”)。一共2~4條指令(視不同CPU指令集而定)谁撼。

換了解釋性語言歧胁,情況就大大不同了。

它得先把“x = 1234+5678”當成字符串厉碟,逐個字符比對以分析語法結(jié)構(gòu)——不計空格這也是11個字符喊巍,至少要做11個循環(huán);每個循環(huán)至少需要執(zhí)行的指令有:取數(shù)據(jù)(如讀'x'這個字符)箍鼓、比較數(shù)據(jù)崭参、根據(jù)比較結(jié)果跳轉(zhuǎn)(可能還得跳轉(zhuǎn)回來)、累加循環(huán)計數(shù)器款咖、檢查循環(huán)計數(shù)器是否到達終值何暮、根據(jù)比較結(jié)果跳轉(zhuǎn)。這就是至少6條指令铐殃,其中包含一次內(nèi)存讀取海洼、至少兩次分支指令(現(xiàn)代CPU有分支預(yù)測,若命中無額外消耗背稼,否則……)贰军。總計66條指令,比編譯型語言慢至少17倍(假設(shè)每條指令執(zhí)行時間相同词疼。但事實上俯树,訪存/跳轉(zhuǎn)類指令消耗的時間常常是加法指令的十倍甚至百倍)。

這還只是讀入源碼的消耗贰盗,尚未計入“語法分析”這個大頭许饿;加上后,起碼指令數(shù)多數(shù)百倍(消耗時間嘛……我猜起碼得多數(shù)千倍吧)舵盈。

不過陋率,python比起其它解釋性語言還是強很多的。因為它可以事先把文本代碼編譯成“字節(jié)碼”(存儲于擴展名為pyc的文件里)秽晚,從而直接處理整型的“指令代碼”瓦糟,不再需要從頭開始分析文本。

但是赴蝇,從“字節(jié)碼”翻譯到實際CPU代碼這步菩浙,仍然是省不下的。

這個消耗句伶,可看作“利用虛擬機”執(zhí)行異構(gòu)CPU上的程序劲蜻。有人證明過,哪怕優(yōu)化到極致考余,這也需要10倍的性能消耗先嬉。

這個消耗也有辦法縮減。這就是JIT技術(shù)楚堤。

JIT說白了疫蔓,就是在第一遍執(zhí)行一段代碼前,先執(zhí)行編譯動作钾军,然后執(zhí)行編譯后的代碼鳄袍。

如果代碼中沒有循環(huán),那么這將白白付出很多額外的時間代價吏恭;但若有一定規(guī)模以上的循環(huán)拗小,就可能節(jié)省一點時間。

這里面的佼佼者是Java樱哼。它甚至能根據(jù)上次運行結(jié)果實時profile哀九,然后花大力氣優(yōu)化關(guān)鍵代碼,從而得到比C更快的執(zhí)行速度搅幅。

不過阅束,理想很豐滿,現(xiàn)實很骨感茄唐。雖然局部熱點的確可能更快息裸,但Java的整體效率仍然比C/C++差上很多——這個原因就比較復(fù)雜了蝇更。

和C/C++/Java那種投入海量資源經(jīng)過千錘百煉的編譯器不同,python的JIT甚至可稱得上“蹩腳”呼盆。

加加減減年扩,僅一個循環(huán),慢上十幾甚至幾十倍還是很正常的访圃。

以上討論厨幻,僅僅考慮了for循環(huán)這個控制結(jié)構(gòu)本身。事實上腿时,“慢”往往是全方位的况脆。

舉例來說,要計算一組向量批糟,首先就要存儲它格了。

怎么存儲呢?

對C/C++來說徽鼎,就存在“數(shù)組”里笆搓;而它的數(shù)組,就是赤裸裸的一片連續(xù)內(nèi)存區(qū)域纬傲;區(qū)域中每若干個字節(jié)就存儲了一個數(shù)值數(shù)據(jù)。

這種結(jié)構(gòu)CPU處理起來最為方便快捷肤频,且cache友好(若cache不友好就可能慢數(shù)倍甚至數(shù)十倍)叹括。

Java等其它語言就要稍遜一籌。因為它的“數(shù)組”是“真正的數(shù)組”宵荒;相對于“連續(xù)內(nèi)存區(qū)域”汁雷,“真正的數(shù)組”就不得不在每次訪問時檢查數(shù)組下標有無越界。這個檢查開銷不大报咳,但也不小……

當然侠讯,這也是有好處的。至少不用像C/C++那樣暑刃,整天擔心緩沖區(qū)溢出了厢漩。

而python之類……

為了遷就初學(xué)者,它去掉了“變量聲明”以及“數(shù)據(jù)類型”——于是它的用戶再也用不著岩臣、也沒法寫 int xxx了溜嗜。隨便什么數(shù)據(jù),咱想存就存架谎,烏拉炸宵!

但是,如果我告訴你谷扣,可變數(shù)據(jù)類型其實在C/C++里面是這樣聲明的呢:

typedef struct tagVARIANT {
  union {
    struct __tagVARIANT {
      VARTYPE vt;
      WORD    wReserved1;
      WORD    wReserved2;
      WORD    wReserved3;
      union {
        LONGLONG            llVal;
        LONG                lVal;
        BYTE                bVal;
        SHORT               iVal;
        FLOAT               fltVal;
        DOUBLE              dblVal;
        VARIANT_BOOL        boolVal;
        _VARIANT_BOOL       bool;
        SCODE               scode;
        CY                  cyVal;
        DATE                date;
        BSTR                bstrVal;
        IUnknown            *punkVal;
        IDispatch           *pdispVal;
        SAFEARRAY           *parray;
        BYTE                *pbVal;
        SHORT               *piVal;
        LONG                *plVal;
        LONGLONG            *pllVal;
        FLOAT               *pfltVal;
        DOUBLE              *pdblVal;
        VARIANT_BOOL        *pboolVal;
        _VARIANT_BOOL       *pbool;
        SCODE               *pscode;
        CY                  *pcyVal;
        DATE                *pdate;
        BSTR                *pbstrVal;
        IUnknown            **ppunkVal;
        IDispatch           **ppdispVal;
        SAFEARRAY           **pparray;
        VARIANT             *pvarVal;
        PVOID               byref;
        CHAR                cVal;
        USHORT              uiVal;
        ULONG               ulVal;
        ULONGLONG           ullVal;
        INT                 intVal;
        UINT                uintVal;
        DECIMAL             *pdecVal;
        CHAR                *pcVal;
        USHORT              *puiVal;
        ULONG               *pulVal;
        ULONGLONG           *pullVal;
        INT                 *pintVal;
        UINT                *puintVal;
        struct __tagBRECORD {
          PVOID       pvRecord;
          IRecordInfo *pRecInfo;
        } __VARIANT_NAME_4;
      } __VARIANT_NAME_3;
    } __VARIANT_NAME_2;
    DECIMAL             decVal;
  } __VARIANT_NAME_1;
} VARIANT, *LPVARIANT, VARIANTARG, *LPVARIANTARG;

簡單說土全,這玩意兒的思路就是“利用一個tag指示數(shù)據(jù)類型,真正的數(shù)據(jù)存儲在下面的union里;訪問時裹匙,依據(jù)tag指示轉(zhuǎn)換/返回合適類型”瑞凑。

很顯然,對C/C++/Java程序員來說幻件,這玩意兒無論時間還是空間上拨黔,都是個災(zāi)難。

并且绰沥,它也極度的cache不友好——本來可以連續(xù)存儲的篱蝇,現(xiàn)在……變成了個結(jié)構(gòu)體;而且一旦存了某些類型的數(shù)據(jù)徽曲,就不得不通過指針跳轉(zhuǎn)到另一塊區(qū)域才能訪問(如果原地存儲零截,浪費的空間就太恐怖了)。

所以你看秃臣,咱要基于這種結(jié)構(gòu)談效率涧衙,是不是有點……

哪怕僅僅了解到這個程度也已經(jīng)很是觸目驚心了:解釋執(zhí)行+字節(jié)碼優(yōu)化慢上至少10倍到幾十上百倍,“初學(xué)者友好”的基礎(chǔ)數(shù)據(jù)又慢上幾倍到幾十倍奥此,透過容器訪問(而非性能更好的弧哎、固定大小數(shù)組乃至不檢查下標假裝自己是數(shù)組的“內(nèi)存區(qū)域”)再慢上幾倍到幾十倍……哪怕咱暫時不考慮其它機制帶來的開銷,僅把這幾樣往一塊一湊(在某些特定的情況下稚虎,這些不同的“慢”點還可能相互影響撤嫩、起到“遲緩度倍增放大”的效果)……

除此之外,還有python內(nèi)部如何管理/索引/訪問腳本中的全局/局部變量的問題(一般會用dict)蠢终、用戶數(shù)據(jù)和物理機存儲器嚴重不匹配引起的緩存未命中問題序攘、python內(nèi)部狀態(tài)機/執(zhí)行現(xiàn)場管理等等方面管理的問題——對編譯型語言,這些統(tǒng)統(tǒng)不存在寻拂,CPU/內(nèi)存自己就把自己照顧的很好了程奠;但對解釋性語言,這些都會成為“遲緩度倍增”的元兇祭钉。

這些東西的相互影響極為復(fù)雜微妙瞄沙,幾乎沒人能徹底搞明白它。

你看朴皆,明白了前因后果帕识,咱是不是只能說“python的優(yōu)化實在不錯,才僅僅慢了20萬倍而已”呢遂铡?(笑~

當然肮疗,如果不做這類較為復(fù)雜的處理,僅僅是一些流程性的東西的話扒接,這類語言的處理速度還是夠用的——至少與之交互的人感受不到絲毫延遲伪货。

甚至们衙,哪怕需要復(fù)雜的處理,這類語言也可以向其它語言求救啊碱呼。就好像有個numpy蒙挑,誰敢說python做不了向量運算呢?

——當然愚臀,和行家說話時忆蚀,你得明白,這是找C之類語言搬救兵了姑裂。睜眼說瞎話把它當成python語言自己的能力是有點丟人的馋袜。不過如果只混python的圈子的話,這倒也不耽誤什么舶斧。

————————————————————————————

如果要揭短欣鳖,專業(yè)程序員還會把無數(shù)據(jù)類型導(dǎo)致接口模糊所以無法寫較為復(fù)雜的程序之類弊端給你列出一火車的。但這些就是沒必要的題外話了茴厉。

畢竟泽台,python只是個膠水語言,初學(xué)者友好并且應(yīng)付常見的簡單應(yīng)用場景綽綽有余矾缓,這已經(jīng)足夠了怀酷。

就好像把office做的傻瓜化,本就是專業(yè)程序員的工作一樣——用戶覺得好用嗜闻、樂意掏錢就行了胰坟,何必關(guān)心“做出一套office需要砸進去的錢足夠蓋N座迪拜塔”呢。

當然泞辐,如果想進一步發(fā)展的話,請記住“在合適的地方用合適的工具”這句話——然后想辦法搞明白每種工具的局限性吧竞滓。

畢竟咐吼,哪怕是C/C++,在做矩陣之類運算時商佑,也還會求助于SIMD的MMX指令锯茄、超線程/多核心CPU乃至GPU,以便為自己“增補”上并行處理能力呢茶没。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肌幽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抓半,更是在濱河造成了極大的恐慌喂急,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笛求,死亡現(xiàn)場離奇詭異糕簿,居然都是意外死亡懂诗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門界阁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贮竟,“玉大人咕别,你說我怎么就攤上這事惰拱。” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵声旺,是天一觀的道長鉴扫。 經(jīng)常有香客問我鹦赎,道長锁施,這世上最難降的妖魔是什么肩狂? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任傻谁,我火速辦了婚禮,結(jié)果婚禮上列粪,老公的妹妹穿的比我還像新娘审磁。我一直安慰自己,他們只是感情好岂座,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布态蒂。 她就那樣靜靜地躺著,像睡著了一般费什。 火紅的嫁衣襯著肌膚如雪钾恢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天鸳址,我揣著相機與錄音瘩蚪,去河邊找鬼。 笑死稿黍,一個胖子當著我的面吹牛募舟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闻察,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼琢锋!你這毒婦竟也來了辕漂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吴超,失蹤者是張志新(化名)和其女友劉穎钉嘹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲸阻,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡跋涣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年缨睡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陈辱。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奖年,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沛贪,到底是詐尸還是另有隱情陋守,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布利赋,位于F島的核電站水评,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏媚送。R本人自食惡果不足惜中燥,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望塘偎。 院中可真熱鬧疗涉,春花似錦、人聲如沸式塌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽峰尝。三九已至偏窝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間武学,已是汗流浹背祭往。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留火窒,地道東北人硼补。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像熏矿,于是被迫代替她去往敵國和親已骇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354