沈崴 - 「PYTHON 為什么是最快的語(yǔ)言(上)」QA

本文將對(duì)視頻「PYTHON 為什么是最快的語(yǔ)言(上)」中的內(nèi)容進(jìn)行進(jìn)一步的展開(kāi) ① 。

一朦前、如果說(shuō) Python 協(xié)程是個(gè)優(yōu)勢(shì)瀑踢,那其他語(yǔ)言難道就沒(méi)有協(xié)程嗎? ②

對(duì)其他語(yǔ)言來(lái)說(shuō)“協(xié)程”或許是近年來(lái)才流行起來(lái)的一種“新技術(shù)”遗锣,但是對(duì) Python 來(lái)說(shuō),協(xié)程早在 1998 年就已經(jīng)出現(xiàn)并且大規(guī)模使用至今 —— 雖然從表面上看,各種語(yǔ)言的協(xié)程實(shí)現(xiàn)都大同小異,比如 Goroutine 和 gevent.patch_all() 都可以直接把整個(gè)語(yǔ)言協(xié)程化讲岁,使用起來(lái)非常方便 —— 似乎“只要增加了協(xié)程這個(gè)新特性就可以在任意語(yǔ)言中直接使用了”逞刷,但實(shí)際上協(xié)程具有極深的技術(shù)棧。Python 在之前的 20 多年里,在積累上的領(lǐng)先,讓 Python 和其他語(yǔ)言有了本質(zhì)上的區(qū)別 —— “可以大規(guī)模商用”和“玩具”的區(qū)別。下面我來(lái)簡(jiǎn)單摘取幾個(gè)方面叶组,來(lái)說(shuō)明在這之前的 20 多年里面,Python 可以在協(xié)程技術(shù)上比“后起之秀們”多做多少事情历造。

1甩十、調(diào)度器

和其他語(yǔ)言不同,Python 在長(zhǎng)時(shí)間的積累中形成了大量可用的協(xié)程調(diào)度器帕膜,包括“搶占式”枣氧、“非搶占式”、“全功能”垮刹、“高性能”等各種實(shí)現(xiàn)达吞,以適用于各種應(yīng)用場(chǎng)景的需求。我使用過(guò)的調(diào)度器就有 Stackless Python荒典、Gevent酪劫、Curio 以及 Eurasia ③ 等 —— 在這里吞鸭,Eurasia 是我于 2007 年開(kāi)源的協(xié)程服務(wù)器框架,使用了我自己開(kāi)發(fā)的輕量級(jí)協(xié)程調(diào)度器覆糟,在性能上具有相當(dāng)大的優(yōu)勢(shì)刻剥。而在我開(kāi)源的另一種協(xié)程服務(wù)器框架 Slowdown ④ 中,我則是使用了在功能特性上更為全面的 Gevent 作為調(diào)度器滩字,更加適合用于搭建復(fù)雜應(yīng)用造虏。在不同的應(yīng)用場(chǎng)景下能夠擁有如此之多的選擇,是 Python 在協(xié)程技術(shù)上麦箍,成熟性的體現(xiàn)漓藕。我開(kāi)發(fā)的 Eurasia 發(fā)布于 2007 年,并在接下來(lái)的時(shí)間里一直在投入使用 —— 直到 6 年以后 Goroutine 才開(kāi)始出現(xiàn) —— 而此時(shí)挟裂,距離 Python 著名的大規(guī)模協(xié)程應(yīng)用 EVE online 的推出享钞,更已經(jīng)經(jīng)過(guò) 10 年。

2诀蓉、專業(yè)性

協(xié)程在“棧切換”“調(diào)用上下文”上栗竖,和一般程序具有很大的差別,特別是和線程池渠啤、多進(jìn)程配合使用時(shí)狐肢,情況會(huì)更加復(fù)雜。如果沒(méi)有技術(shù)積累沥曹,開(kāi)發(fā)者往往不知道“代碼在何種情況下會(huì)出現(xiàn)意外情況”处坪,在出現(xiàn)錯(cuò)誤時(shí)也不知道該如何正確地去調(diào)試 —— 這讓協(xié)程技術(shù)雖然門(mén)檻很低,入門(mén)容易架专,但是在實(shí)際使用中,它會(huì)變成一個(gè)巨型的“天坑”—— 而 Python 長(zhǎng)期在協(xié)程上的實(shí)踐玄帕,形成了大量的技術(shù)積累部脚,和無(wú)數(shù)的專家用戶。這讓你在遇到困難時(shí)裤纹,更容易查閱到解決方案委刘,并獲得更多的幫助 —— 而不是讓你在協(xié)程技術(shù)領(lǐng)域中拓荒、踩坑鹰椒。

3锡移、文件/數(shù)據(jù)庫(kù)

要在協(xié)程架構(gòu)中真正實(shí)現(xiàn)高并發(fā),就必須要把系統(tǒng)中的所有操作全部進(jìn)行異步化 —— 任何沒(méi)有異步化的節(jié)點(diǎn)全部都會(huì)變成性能瓶頸 —— 這相當(dāng)于是要把整個(gè)操作系統(tǒng)都進(jìn)行異步化改造漆际,如果沒(méi)有像 Python 這樣淆珊,經(jīng)過(guò)了漫長(zhǎng)的積累,是無(wú)法做到的奸汇。其中“文件”和“數(shù)據(jù)庫(kù)”就是很難異步化的一個(gè)例子 —— 像 Python 中著名的 Tornado 就是只實(shí)現(xiàn)了網(wǎng)絡(luò)部分的“協(xié)程異步化”施符,卻對(duì)文件 IO 無(wú)能為力的一個(gè)典型案例往声。這樣就只能被迫使用“基于多線程的應(yīng)用層框架”,以“傳統(tǒng)的多線程模式”來(lái)處理文件和數(shù)據(jù)庫(kù)的請(qǐng)求戳吝。因此 Tornado 在事實(shí)上浩销,仍然只是一個(gè)傳統(tǒng)的,受限于“文件 IO 瓶頸”以及“線程數(shù)量”的服務(wù)器框架听哭,同時(shí)數(shù)據(jù)庫(kù)服務(wù)器也會(huì)變成整個(gè)系統(tǒng)的瓶頸慢洋。其他像 Goroutine 協(xié)程,在遇到文件及數(shù)據(jù)庫(kù)時(shí)陆盘,也同樣會(huì)退化為線程池普筹,這也是其他各種語(yǔ)言的常態(tài) —— 其實(shí)在 Python 世界中,以我自己為例礁遣,在使用協(xié)程的十幾二十年的時(shí)間里斑芜,早已完成“緩存式文件 IO”及“數(shù)據(jù)庫(kù)底層磁盤(pán) IO”的“協(xié)程異步化”改造。這意味著高級(jí)的 Python 協(xié)程系統(tǒng)祟霍,不僅可以同時(shí)響應(yīng)“百萬(wàn)級(jí)的網(wǎng)絡(luò) IO”杏头,也能處理同等并發(fā)規(guī)模的“文件讀寫(xiě)”和“SQL 請(qǐng)求”。這使得 Python 的協(xié)程可以貫穿整個(gè)系統(tǒng)沸呐,沒(méi)有任何一個(gè)節(jié)點(diǎn)成為瓶頸醇王。

二、說(shuō) Python 的運(yùn)行速度快是不是太扯了崭添?

視頻中的觀點(diǎn)是“程序性能只和瓶頸有關(guān)”寓娩,“語(yǔ)言快不快只和它擁有多少繞過(guò)瓶頸的手段有關(guān)”。開(kāi)發(fā)效率越高呼渣,語(yǔ)言可以實(shí)現(xiàn)的性能優(yōu)化就會(huì)越多棘伴,程序也就越快 —— 表面上可以用其他語(yǔ)言來(lái)改寫(xiě) Python 程序來(lái)獲得更高的執(zhí)行效率,但是可能會(huì)花上好幾倍的時(shí)間 —— 而在同樣這段時(shí)間里 Python 已經(jīng)可以寫(xiě)出更快的實(shí)現(xiàn)了屁置。

在 PyPy 出現(xiàn)以前焊夸,Python 主要的 JIT 還是 Psyco 的時(shí)候,開(kāi)發(fā)者就已經(jīng)需要在 Unix/Win32 等不同平臺(tái)下來(lái)維護(hù) Psyco 的 32 位版本了蓝角,但隨著 64 位 CPU 的逐漸普及阱穗,要想同時(shí)維護(hù)多個(gè) OS,以及 MIPS/ARM/x86 等 CPU 架構(gòu)使鹅,并同時(shí)維護(hù) 32 位和 64 位 CPU 下的版本揪阶,這讓維護(hù) Psyco 本身就變得非常吃力,在 JIT 算法上進(jìn)行突破就變得更加不可能了患朱。于是 64 位 CPU 的推出鲁僚,成了壓跨項(xiàng)目的最后一根稻草,Psyco 的開(kāi)發(fā)者在一篇文章中最后提到 ⑤ ,他發(fā)現(xiàn)只有依賴 Python 的開(kāi)發(fā)效率才能讓 JIT 更進(jìn)一步蕴茴,尤其是在開(kāi)發(fā)階段 Python 可以直接解釋執(zhí)行的特點(diǎn)讓 JIT 的調(diào)試工作變得非常容易劝评。所以 Pysco 的開(kāi)發(fā)者停止了 Psyco 項(xiàng)目,并轉(zhuǎn)而開(kāi)創(chuàng)了 PyPy 項(xiàng)目 —— 這說(shuō)明當(dāng)一個(gè)項(xiàng)目的“復(fù)雜性”和“工作量”到達(dá)一定級(jí)別時(shí)倦淀,Python 已經(jīng)不是僅僅能夠“快過(guò)”C 語(yǔ)言這么簡(jiǎn)單了蒋畜,事情會(huì)變成“如果不使用 Python,問(wèn)題可能根本就沒(méi)有辦法解決”的程度撞叽。故而在視頻里“JIT 開(kāi)發(fā)”被當(dāng)作典型案例來(lái)使用 —— 當(dāng)然這肯定不是孤例姻成。

在許多時(shí)候,我們可能只是把 Python 當(dāng)作“能跑起來(lái)的偽代碼”用來(lái)進(jìn)行“快速原型開(kāi)發(fā)”愿棋,一旦原型驗(yàn)證完成科展,程序員會(huì)有一個(gè)本能的沖動(dòng),就是把它修改成“正式”的 C 語(yǔ)言版本來(lái)“一勞永逸”糠雨、“以利千秋”才睹。IBus 輸入法便是從一個(gè) Python 項(xiàng)目逐漸 C++ 化,并讓性能得到提升的一個(gè)很好的例子甘邀,但是更多的 Python 項(xiàng)目卻并沒(méi)有像意料中那樣琅攘,最終走上 C 語(yǔ)言化的這條道路,而是依舊堅(jiān)持使用 Python 語(yǔ)言來(lái)進(jìn)行開(kāi)發(fā)松邪。所以在“從‘Python 快速原型開(kāi)發(fā)’到‘C 語(yǔ)言最終正式版’”的這個(gè)演進(jìn)劇本里一定是出現(xiàn)了某種變數(shù) —— 當(dāng)開(kāi)發(fā)者準(zhǔn)備替換 Python 時(shí)坞琴,他們發(fā)現(xiàn):

  1. 如果使用其他語(yǔ)言來(lái)強(qiáng)行改寫(xiě) Python 程序,由于瓶頸可能并不在語(yǔ)言這個(gè)層面逗抑,可能根本就無(wú)法在性能上得到可觀的提升剧辐。
  2. 如果不使用 Python,開(kāi)發(fā)和維護(hù)的成本可能會(huì)大幅升高到無(wú)法承受的程度邮府,強(qiáng)行替換 Python 只會(huì)讓程序在“功能”和“性能”上全面落后于 Python 版本的升級(jí)速度荧关。

這指向一點(diǎn):只要存在大量的 Python 項(xiàng)目,在經(jīng)過(guò)無(wú)數(shù)次升級(jí)后褂傀,仍然沒(méi)有被 C 語(yǔ)言化羞酗,那么在這些項(xiàng)目中,Python 的執(zhí)行性能肯定不會(huì)輸于其他語(yǔ)言紊服。“這些項(xiàng)目其實(shí)‘都’是 Python 程序運(yùn)行速度快的例子”胸竞。

在后面我即將發(fā)布的該系列視頻的第二集「PYTHON 為什么是最快的語(yǔ)言(時(shí)空篇)」中欺嗤,我會(huì)提到,由于 Python 語(yǔ)言簡(jiǎn)潔的特性卫枝,會(huì)讓程序看上去都非常簡(jiǎn)單煎饼,這掩蓋了每個(gè) Python 程序事實(shí)上的復(fù)雜度,并給人以一個(gè)錯(cuò)覺(jué)校赤,以為用其他語(yǔ)言“也”可以輕松地寫(xiě)出具有同等復(fù)雜度的程序吆玖。而等到真正用其他語(yǔ)言來(lái)著手實(shí)施的時(shí)候筒溃,才發(fā)現(xiàn)要實(shí)現(xiàn)同等復(fù)雜度的程序,語(yǔ)言性能下降的幅度沾乘,最終會(huì)出乎所有人的意料怜奖。

三、Python 在執(zhí)行效率上會(huì)不會(huì)被編譯型語(yǔ)言吊打翅阵?

一個(gè)例子是 Java 的 WEB 服務(wù)常常會(huì)給人以性能不行的印象歪玲,然而事實(shí)卻是,Java 是一種逐行執(zhí)行性能很高的語(yǔ)言掷匠,Java 的 socket 服務(wù)器其實(shí)也擁有相當(dāng)不錯(cuò)的性能滥崩。但是當(dāng)把應(yīng)用邏輯加進(jìn)來(lái)以后,為了漂亮地解決各種業(yè)務(wù)邏輯讹语,服務(wù)架構(gòu)變得異常復(fù)雜和龐大钙皮,以至于曾經(jīng)出現(xiàn)過(guò)著名的“厚膠合層怪物”:“J2EE”,并最終把 Java 服務(wù)的性能從高空拽下地板顽决。這說(shuō)明在應(yīng)用領(lǐng)域短条,“逐行執(zhí)行性能”并不是“最終真實(shí)性能”的決定性因素。只有抑制住讓架構(gòu)無(wú)限膨脹的沖動(dòng)擎值,才能在性能上最終勝出慌烧。PHP 這種“最好的編程語(yǔ)言”就曾長(zhǎng)期被人批評(píng)為“不夠面向?qū)ο蟆薄ⅰ叭鄙倨髽I(yè)級(jí)開(kāi)發(fā)能力”—— 但無(wú)論如何鸠儿,在客觀上屹蚊,這使得 PHP 很難寫(xiě)出癡腫的應(yīng)用架構(gòu),這導(dǎo)致 PHP 編寫(xiě)的 WEB 服務(wù)进每,最終無(wú)論是在“性能”上還是在“功能”上都相當(dāng)出色汹粤。

感謝 Python 出色的開(kāi)發(fā)效率,重新制造一個(gè)輪子變成了一件似乎沒(méi)有什么成本的事情田晚,所以在 Python 中誕生了無(wú)數(shù)的 WEB 框架嘱兼,這是一個(gè)只有在 Python 世界才會(huì)出現(xiàn)的獨(dú)特現(xiàn)象 —— 其中就包括我開(kāi)源的“Slowdown”(及前面提到的“Eurasia” 等)項(xiàng)目。這是一個(gè)從 socket 底層到應(yīng)用層贤徒,都完全使用 Python 開(kāi)發(fā)的服務(wù)器框架芹壕。那么問(wèn)題來(lái)了,這里使用 Python 來(lái)進(jìn)行應(yīng)用層開(kāi)發(fā)是可以理解的接奈,但是為什么連 socket 底層都要使用 Python 來(lái)開(kāi)發(fā)踢涌,這不會(huì)有性能問(wèn)題嗎?雖然有點(diǎn)違反直覺(jué)序宦,但我的答案是:“使用 Python 進(jìn)行底層開(kāi)發(fā)只會(huì)讓服務(wù)器性能變得更好”睁壁。

首先,使用 C 語(yǔ)言來(lái)處理 socket 通信和 IO 調(diào)度,的確會(huì)讓服務(wù)器的底層性能有所提升潘明。但是我們無(wú)可避免地要將底層接口“封裝”給 Python 等其他語(yǔ)言的“應(yīng)用層框架”使用行剂,這就涉及到了“底層”到“應(yīng)用層”的接口轉(zhuǎn)換,在轉(zhuǎn)換過(guò)程中钳降,相當(dāng)一部分“底層接口”的“靈活性”將無(wú)可避免地犧牲掉了厚宰。

  1. 為了彌補(bǔ)這部分靈活性的損失,“底層”和“應(yīng)用層”需要被迫調(diào)整出更加復(fù)雜龐大的封裝架構(gòu)牲阁,增加更多的接口固阁,來(lái)應(yīng)付一些涉及到“底層”的需求。
  2. 如果底層和應(yīng)用層實(shí)現(xiàn)了徹底的封裝隔離城菊,許多通過(guò)操作底層可以直接輕松處理掉的需求將無(wú)法實(shí)現(xiàn)备燃,或者只能通過(guò)其他相對(duì)復(fù)雜的方法來(lái)“近似”實(shí)現(xiàn)。

如果你是某個(gè)應(yīng)用框架的用戶凌唬,看到這里或許會(huì)有一種似曾相識(shí)的感覺(jué)并齐。因?yàn)樵?jīng)有個(gè)功能你發(fā)現(xiàn)框架并沒(méi)有提供,而要把這個(gè)功能加上客税,則需要操作框架底層的權(quán)利况褪,但是由于底層已經(jīng)被“封裝”得“很好”是無(wú)法接觸的 —— 這最終讓你動(dòng)彈不得 —— 而當(dāng)問(wèn)題被提交給框架的維護(hù)者時(shí),解決方法往往是“為每個(gè)新增的問(wèn)題設(shè)計(jì)出更多的接口”更耻、“重構(gòu)出更好的架構(gòu)”测垛。這樣,框架的復(fù)雜度就會(huì)逐漸滑入失控秧均,變得臃腫食侮,最終讓性能大幅下降。

而使用 Python 來(lái)統(tǒng)一開(kāi)發(fā)“應(yīng)用層框架”和“服務(wù)器底層”目胡,則可以避免讓“底層”和“應(yīng)用層”進(jìn)行跨語(yǔ)言接口轉(zhuǎn)換與封裝 —— 進(jìn)一步锯七,可以讓“底層直接穿透到頂層”,并最終讓“底層和應(yīng)用層完全合并”誉己,膠合層消失眉尸,框架(framework)程序庫(kù)(library)化。最終實(shí)現(xiàn)架構(gòu)簡(jiǎn)化巨双,和性能反超噪猾。

而這,對(duì) Slowdown 之類(lèi)的協(xié)程服務(wù)器具有著更加非凡的意義筑累。由于協(xié)程是用之不竭的畏妖,所以可以“利用協(xié)程來(lái)維護(hù)‘無(wú)數(shù)’長(zhǎng)連接的上下文”來(lái)“等待長(zhǎng)時(shí)間執(zhí)行結(jié)果”而無(wú)需受到“傳統(tǒng)架構(gòu)下線程數(shù)量的限制”,不需要通過(guò)“中斷連接上下文”來(lái)“釋放有限的線程”用額外的緩存架構(gòu)和消息隊(duì)列來(lái)傳遞“長(zhǎng)時(shí)間的執(zhí)行結(jié)果”疼阔。這樣就可以把大量的因“受限于傳統(tǒng)線程架構(gòu)”而增加的中間架構(gòu)(中間服務(wù)器、IPC)給精簡(jiǎn)掉了。而此時(shí)婆廊,由于應(yīng)用層中“長(zhǎng)時(shí)間任務(wù)的上下文”和底層的“長(zhǎng)連接上下文”是緊密耦合的迅细,所以在這里“底層穿透性”就尤為重要了。

結(jié)合上述內(nèi)容淘邻,我們不難發(fā)現(xiàn)使用 Python 將底層與應(yīng)用層進(jìn)行融合茵典,最終可以起到將整個(gè)系統(tǒng)的架構(gòu)大幅簡(jiǎn)化的驚人效果。這樣匪夷所思的結(jié)果出現(xiàn)了:使用 Python 進(jìn)行服務(wù)器底層開(kāi)發(fā)宾舅,在整個(gè)系統(tǒng)層面反而取得了更好的性能统阿。目前有大量的基于 Gevent 或者 asyncio 等框架的 Python 項(xiàng)目,仗著自己是協(xié)程架構(gòu)筹我,性能起點(diǎn)高扶平,不管有意無(wú)意,通常會(huì)采用從網(wǎng)絡(luò)底層到應(yīng)用層的全棧式設(shè)計(jì)蔬蕊,雖然沒(méi)有用到編譯型語(yǔ)言來(lái)“優(yōu)化”底層结澄,卻在 benchmark 中都具有不錯(cuò)的成績(jī),這又成為 Python “直接用于性能敏感的底層開(kāi)發(fā)”岸夯,并“在性能上超過(guò)包括編譯型語(yǔ)言在內(nèi)的其他語(yǔ)言”的典型案例麻献。

四、和其他解釋性語(yǔ)言的性能比較

下面我們來(lái)談一下視頻中提到的“開(kāi)發(fā)效率 == 運(yùn)行效率”這一條猜扮,是否同樣可以套用在其他語(yǔ)言上的這個(gè)問(wèn)題勉吻。簡(jiǎn)單來(lái)說(shuō),答案是肯定的 —— 由于在“公平”“同等”的時(shí)間里旅赢,開(kāi)發(fā)效率高的編程語(yǔ)言一定可以實(shí)現(xiàn)更多的“優(yōu)化”繞過(guò)更多的“瓶頸”齿桃,任何語(yǔ)言只要能夠擁有極致的開(kāi)發(fā)效率,那么(在不是強(qiáng)烈依賴于逐行性能的多數(shù)場(chǎng)景下)一定會(huì)擁有不錯(cuò)的運(yùn)行效率鲜漩。這樣在許多時(shí)候源譬,程序“運(yùn)行效率”的比拼,其實(shí)就是“開(kāi)發(fā)效率”的比拼孕似。那么其他語(yǔ)言是否擁有和 Python 一樣的開(kāi)發(fā)效率呢踩娘?這就是一個(gè)非常依賴于主觀判斷的問(wèn)題了。所以我試圖采用一個(gè)“更為客觀”的標(biāo)準(zhǔn)來(lái)對(duì)編程語(yǔ)言的開(kāi)發(fā)效率進(jìn)行評(píng)價(jià):“有效項(xiàng)目數(shù)”—— 如果一種語(yǔ)言的開(kāi)發(fā)效率很高喉祭,那么在“足夠長(zhǎng)的時(shí)間段”和“相同的時(shí)間”里养渴,它一定會(huì)開(kāi)發(fā)出比別人更多的項(xiàng)目,“產(chǎn)量更高”泛烙。在翻閱了 Github 的“Ranking”理卑、“Trending”、“Stars”中的項(xiàng)目之后蔽氨,我發(fā)現(xiàn)這些指標(biāo)幾乎都被兩大語(yǔ)言所“霸榜”:“Javascript”和“Python”藐唠。這意味著“單從客觀指標(biāo)上來(lái)講”帆疟,大部分語(yǔ)言的開(kāi)發(fā)效率尚不能達(dá)到 Python 的級(jí)別。依賴于開(kāi)發(fā)效率宇立,Python 在單位時(shí)間里就可以生產(chǎn)出更多的優(yōu)化算法踪宠,繞過(guò)更多瓶頸,從而實(shí)現(xiàn)更高的執(zhí)行效率妈嘹。這樣 PYTHON 作為最快的編程語(yǔ)言柳琢,可以說(shuō)實(shí)至名歸。

五润脸、“靠開(kāi)發(fā)效率省下時(shí)間優(yōu)化算法”是否足以讓 Python 獲得比 C 更好的性能(況且很多業(yè)務(wù)代碼根本不存在多大優(yōu)化空間)柬脸,這點(diǎn)需要有說(shuō)服力的實(shí)踐來(lái)舉例論證。

首先我們來(lái)思考一個(gè)業(yè)務(wù)問(wèn)題毙驯,在登錄倒堕、驗(yàn)證的場(chǎng)景中 Python 每秒能夠生成多少個(gè)圖形驗(yàn)證碼?當(dāng)驗(yàn)證碼的并發(fā)請(qǐng)求數(shù)太多尔苦,超出程序處理能力的時(shí)候涩馆,我們應(yīng)該如何去優(yōu)化?

最簡(jiǎn)單的想法允坚,自然是使用 C 語(yǔ)言來(lái)優(yōu)化出更快的 captcha 生成方法魂那,如果性能還是不夠用,我們就增加服務(wù)器稠项,用來(lái)生成驗(yàn)證碼 —— 不過(guò)在實(shí)際開(kāi)發(fā)中涯雅,我們并不會(huì)真的這么做 —— 今天我主要會(huì)用我“正在使用的”,并且開(kāi)源的 Slowdown 服務(wù)器作為說(shuō)明案例展运。這個(gè)服務(wù)器實(shí)現(xiàn)簡(jiǎn)單活逆、代碼不多,作為作者我又比較熟悉拗胜,拿來(lái)當(dāng)例子會(huì)比較方便蔗候。

1、Captcha

Slowdown 的 captcha.py 模塊埂软,會(huì)預(yù)生成一批隨時(shí)間輪替的驗(yàn)證碼圖片锈遥,當(dāng)請(qǐng)求驗(yàn)證碼圖片時(shí),它不會(huì)通過(guò)“即時(shí)演算”真的去生成一張圖片勘畔,而是會(huì)從“已有的圖片池”中所灸,隨機(jī)抽取出一張圖片來(lái)返回給用戶。這樣每個(gè)用戶就可以拿到不同的圖片炫七,而不同的用戶則可以對(duì)圖片進(jìn)行復(fù)用爬立。在這種情況下,圖片驗(yàn)證碼的生成速度會(huì)快到令人窒息万哪,從而讓性能測(cè)試變得毫無(wú)意義侠驯。

  • 盡管如此抡秆,我還是順手測(cè)了一下,生成 1000000(一百萬(wàn))個(gè)圖形驗(yàn)證碼的時(shí)間大約是 1.9 秒 —— 這就回答了前面的第一個(gè)問(wèn)題“Python 每秒能夠生成多少圖形驗(yàn)證碼”(50萬(wàn)+)吟策。
  • 那么這個(gè)“圖形驗(yàn)證碼的優(yōu)化實(shí)現(xiàn)”需要多少 Python 代碼琅轧?—— 76 行(含注釋)。

2踊挠、接下來(lái)是一個(gè)更大的案例

盡管性能并不是 Slowdown 最主要的設(shè)計(jì)目標(biāo)(所以是我設(shè)計(jì)的服務(wù)器里性能最弱的一款,輕業(yè)務(wù)下冲杀,性能只有我其他 Python 服務(wù)器作品的 1/2~1/3 )效床,但是這種完全使用 Python 開(kāi)發(fā)的服務(wù)器,在性能上與純 C 實(shí)現(xiàn)的服務(wù)器并沒(méi)有特別嚴(yán)重的差距 —— 這里我選擇的比較對(duì)象是 μwsgi权谁,這是一款非常著名的剩檀,使用純 C 開(kāi)發(fā)的異步服務(wù)器。具有和 nginx 相當(dāng)?shù)男阅堋?/p>

下面我們來(lái)做一下測(cè)試旺芽。因?yàn)槎际且呀?jīng)公開(kāi)發(fā)布的開(kāi)源軟件沪猴,下面這些測(cè)試方案也比較簡(jiǎn)單,有興趣的朋友可以自行搭建采章、測(cè)試运嗜。

# 用例: μwsgi
def application(env, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    # connection.execute(SQL)
    return [CONTENT]

# 用例: slowdown
def handler(rw):
    rw.start_response('200 OK', [('Content-Type', 'text/html')])
    # connection.execute(SQL)
    rw.write(CONTENT)
    rw.close()

首先是空載測(cè)試,僅輸出“It works”頁(yè)面悯舟。

Server Software    : uwsgi --http-socket :8080 --wsgi-file test.py > /dev/null 2>&1
Document Length    : 117 bytes
Concurrency Level  : 1000
Requests per second: 22374.22 [#/sec] (mean)
Transfer rate      :  3519.30 [Kbytes/sec] received

Server Software    : slowdown {'handler': 'mytest'}
Document Length    : 117 bytes
Concurrency Level  : 1000
Requests per second: 7238.88 [#/sec] (mean)
Transfer rate      : 1392.64 [Kbytes/sec] received

為了盡量貼近真實(shí)担租,我進(jìn)一步設(shè)計(jì)了一個(gè)簡(jiǎn)單的應(yīng)用場(chǎng)景,完整的服務(wù)器日志輸出抵怎,并模擬一次耗時(shí)約 0.002 秒的數(shù)據(jù)庫(kù)操作(獨(dú)立數(shù)據(jù)庫(kù)服務(wù)器)和 20KB 頁(yè)面奋救。

Server Software    : uwsgi --http-socket :8080 --wsgi-file test.py --logto access.log
Document Length    : 20493 bytes
Concurrency Level  : 1000
Requests per second:  418.94 [#/sec] (mean)
Transfer rate      : 8406.01 [Kbytes/sec] received

Server Software    : slowdown {'handler': 'mytest', 'accesslog': 'access-%Y%m.log'}
Document Length    : 20493 bytes
Concurrency Level  : 1000
Requests per second:  4546.93 [#/sec] (mean)
Transfer rate      : 91302.86 [Kbytes/sec] received

可以發(fā)現(xiàn),當(dāng)沒(méi)有業(yè)務(wù)負(fù)載時(shí)反惕,純 Python 開(kāi)發(fā)的 Slowdown 服務(wù)器尝艘,在性能上大約是純 C 服務(wù)器的 1/3 。但是當(dāng)少量業(yè)務(wù)被部署上去以后姿染,C 服務(wù)器的性能突然出現(xiàn)了大幅下降背亥。最終 Python 服務(wù)器的性能驚人地達(dá)到了 C 服務(wù)器的 10 倍之多(高一個(gè)數(shù)量級(jí))。這正是 Slowdown 的設(shè)計(jì)目標(biāo)之一盔粹,放棄空載性能的爭(zhēng)奪隘梨,著重讓服務(wù)器在具有實(shí)際業(yè)務(wù)負(fù)載的情況下,使性能緩慢下降舷嗡,并在“滿業(yè)務(wù)負(fù)載”的情況下未蝌,最終穩(wěn)定在一個(gè)很高的數(shù)值上。

3之宿、開(kāi)發(fā)效率和性能的關(guān)系

在上面這個(gè)例子中,類(lèi)似 Captcha Token 這樣的聯(lián)合緩存锐峭、磁盤(pán)路徑及靜態(tài)文件緩存,這些幾十行代碼就能帶來(lái)極大性能提升的優(yōu)化隨處可見(jiàn)可婶。使用 Python 來(lái)開(kāi)發(fā)服務(wù)器還能夠省下大量的時(shí)間沿癞,用于全架構(gòu)的體系性優(yōu)化。還可以把瓶頸點(diǎn)用 C 語(yǔ)言進(jìn)行逐點(diǎn)優(yōu)化(正如視頻中所說(shuō)矛渴,Python 和 C 并不是競(jìng)爭(zhēng)關(guān)系椎扬,Python 就是 C)—— 這都是高開(kāi)發(fā)效率下,擁有足夠的時(shí)間才能做到的結(jié)果具温。

Slowdown 僅僅是一個(gè)底層服務(wù)器框架蚕涤,用到的優(yōu)化就已經(jīng)如此之多了。那么在更加復(fù)雜龐大的業(yè)務(wù)層铣猩,將會(huì)擁有多少的優(yōu)化空間揖铜?—— “理論上”C 語(yǔ)言也不是不能“從頭就開(kāi)發(fā)出一樣的程序”,但是像 Python 這樣每隔幾十行就會(huì)有一個(gè)高效算法达皿,做到如此密集的優(yōu)化天吓,實(shí)際上是很難達(dá)到的。因?yàn)槭苤朴陂_(kāi)發(fā)效率峦椰,其他看似“逐行執(zhí)行性能很高”的語(yǔ)言龄寞,最終也只能與 Python 爭(zhēng)一域之得失。業(yè)務(wù)越復(fù)雜们何,工作量越大萄焦,Python 在性能上的優(yōu)勢(shì)就會(huì)越明顯。

六冤竹、GIL 會(huì)影響 Python 使用多核嗎拂封?GIL 會(huì)影響 Python 的性能嗎?

有句話叫做 Unix 程序員永遠(yuǎn)可以把多核用完鹦蠕,Python 也不例外冒签。操作系統(tǒng)本身,各式各樣的服務(wù)钟病,被管道串起來(lái)的進(jìn)程們萧恕,Apache、Nginx肠阱、MySQL票唆、PostgreSQL、Redis屹徘、memcached走趋、MQ …… 都可以把多核用完。不過(guò)噪伊,我們今天來(lái)談另外一個(gè)問(wèn)題簿煌,為什么 GIL 會(huì)影響到 Python 使用多核 —— 一般的看法是 GIL 的存在氮唯,讓 Python 同時(shí)只能允許一個(gè)線程執(zhí)行,這樣 Python 就不能真正的使用多線程姨伟,自然也就不能透過(guò)多線程來(lái)用到多核了惩琉。

但是,為什么要使用多線程夺荒?


① 整理自知乎上的探討 https://www.zhihu.com/question/437926748/answer/1664290088
② 本文分別就以下知乎網(wǎng)友提供的答案進(jìn)行了進(jìn)一步的展開(kāi):(一)潘俊勇(二)季晨曦瞒渠、游漢(三)山海師(四)季晨曦(五)SherlockGy(六)陸海綿
③ Slowdown 基于協(xié)程異步的服務(wù)器及開(kāi)發(fā)框架 https://github.com/wilhelmshen/slowdown
④ Eurasia 基于協(xié)程異步的服務(wù)器及開(kāi)發(fā)框架 https://code.google.com/archive/p/eurasia/
⑤ 由于時(shí)代過(guò)于久遠(yuǎn),該文章的歸檔暫時(shí)沒(méi)有找到技扼≡谛ⅲ可以參閱 Psyco 項(xiàng)目主頁(yè)上“Related project”一節(jié) http://psyco.sourceforge.net/links.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市淮摔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌始赎,老刑警劉巖和橙,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異造垛,居然都是意外死亡魔招,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)五辽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)办斑,“玉大人,你說(shuō)我怎么就攤上這事杆逗∠绯幔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵罪郊,是天一觀的道長(zhǎng)蠕蚜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)悔橄,這世上最難降的妖魔是什么靶累? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮癣疟,結(jié)果婚禮上挣柬,老公的妹妹穿的比我還像新娘。我一直安慰自己睛挚,他們只是感情好邪蛔,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著竞川,像睡著了一般店溢。 火紅的嫁衣襯著肌膚如雪叁熔。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天床牧,我揣著相機(jī)與錄音荣回,去河邊找鬼。 笑死戈咳,一個(gè)胖子當(dāng)著我的面吹牛心软,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播著蛙,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼删铃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了踏堡?” 一聲冷哼從身側(cè)響起猎唁,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎顷蟆,沒(méi)想到半個(gè)月后诫隅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帐偎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年逐纬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片削樊。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豁生,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漫贞,到底是詐尸還是另有隱情甸箱,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布迅脐,位于F島的核電站摇肌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仪际。R本人自食惡果不足惜围小,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望树碱。 院中可真熱鬧肯适,春花似錦、人聲如沸成榜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至刘绣,卻和暖如春樱溉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纬凤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工福贞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人停士。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓挖帘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親恋技。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拇舀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • 目前適用數(shù)據(jù)科學(xué)計(jì)算的語(yǔ)言有不少骄崩,包括:SAS,R薄辅,JAVA刁赖,C/C++,Python等| 總之长搀,Python 雖...
    觀數(shù)據(jù)閱讀 270評(píng)論 0 0
  • Go語(yǔ)言能做什么 一源请、我們?yōu)槭裁催x擇Go語(yǔ)言 選擇Go語(yǔ)言的原因可能會(huì)有很多,關(guān)于Go語(yǔ)言的特性彻况、優(yōu)勢(shì)等谁尸,我們?cè)谥?..
    qfliweimin閱讀 7,262評(píng)論 0 14
  • 久違的晴天,家長(zhǎng)會(huì)纽甘。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí)良蛮,離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)悍赢。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,528評(píng)論 16 22
  • 今天感恩節(jié)哎决瞳,感謝一直在我身邊的親朋好友。感恩相遇左权!感恩不離不棄皮胡。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,573評(píng)論 0 11
  • 可愛(ài)進(jìn)取赏迟,孤獨(dú)成精屡贺。努力飛翔,天堂翱翔。戰(zhàn)爭(zhēng)美好甩栈,孤獨(dú)進(jìn)取泻仙。膽大飛翔,成就輝煌量没。努力進(jìn)取玉转,遙望,和諧家園允蜈≡┒郑可愛(ài)游走...
    趙原野閱讀 2,738評(píng)論 1 1