摘要:的在調(diào)查發(fā)現(xiàn)問(wèn)題在于的性能缺陷后,我們決定嘗試解析器的性能,這是為我們的工具編寫的。這意味著即使忽略任何優(yōu)化,只是將解析器替換為解析器就可以緩解我們的性能瓶頸。
注: 轉(zhuǎn)自 微信公眾號(hào)“高可用架構(gòu)”:從20秒到0.5秒:一個(gè)使用Rust語(yǔ)言來(lái)優(yōu)化Python性能的案例
導(dǎo)讀:Python 被很多互聯(lián)網(wǎng)系統(tǒng)廣泛使用,但在另外一方面,它也存在一些性能問(wèn)題,不過(guò) Sentry 工程師分享的在關(guān)鍵模塊上用另外一門語(yǔ)言 Rust 來(lái)代替 Python 的情況還是比較罕見(jiàn),也在 Python 圈引發(fā)了熱議,高可用架構(gòu)小編將文章翻譯轉(zhuǎn)載如下。
Sentry 是一個(gè)幫助在線業(yè)務(wù)進(jìn)行監(jiān)控及錯(cuò)誤分析的云服務(wù),它每月處理超過(guò)十億次錯(cuò)誤。我們已經(jīng)能夠擴(kuò)展我們的大多數(shù)系統(tǒng),但在過(guò)去幾個(gè)月,Python 寫的 source map 處理程序已經(jīng)成為我們性能瓶頸所在。(譯者:source map 就是將壓縮或者混淆過(guò)的代碼與原始代碼的對(duì)應(yīng)表)
從上周開始,基礎(chǔ)設(shè)施團(tuán)隊(duì)決定調(diào)查 source map 處理程序的性能瓶頸?!覀兊?Javascript 客戶端已經(jīng)成為我們最受歡迎的程序,其中一個(gè)原因是我們通過(guò) source map 反混淆 JavaScript 的能力。然而,處理操作不是沒(méi)有代價(jià)的。我們必須獲取,解壓縮,反混淆然后反向擴(kuò)張,使 JavaScript 堆棧跟蹤可讀。
當(dāng)我們?cè)?4 年前編寫了原始處理流水線時(shí),source map 生態(tài)系統(tǒng)才剛剛開始演化。隨著它成長(zhǎng)為一個(gè)復(fù)雜而成熟的 source map 處理程序,我們花了很多時(shí)間用 Python 來(lái)處理問(wèn)題。
截至昨天,我們通過(guò) Rust 模塊替換我們老的 Python 的 souce map 處理模塊,大大減少了處理時(shí)間和我們的機(jī)器上的 CPU 利用率。
為了解釋這一切,我們需要先理解 source map 和用 Python 的缺點(diǎn)。
Python 的 Source Maps隨著我們的用戶的應(yīng)用程序變得越來(lái)越復(fù)雜,他們的 source map 也越來(lái)越復(fù)雜。在 Python 中解析 JSON 本身是足夠快的,因?yàn)樗鼈冎皇亲址选?strong>問(wèn)題在于反序列化。每個(gè) source map token 產(chǎn)生一個(gè) Python 對(duì)象,我們有一些 source map 可能有幾百萬(wàn)個(gè) token。
將 source map token 反序列化的問(wèn)題使得我們?yōu)榛?Python 對(duì)象支付巨大的成本。另外,所有這些對(duì)象都參與引用計(jì)數(shù)和垃圾收集,這進(jìn)一步增加了開銷。處理 30MB source map 使得單個(gè) Python 進(jìn)程在內(nèi)存中擴(kuò)展到? 800MB,執(zhí)行數(shù)百萬(wàn)次內(nèi)存分配,并使垃圾收集器非常忙碌(譯者注:token 是短生命周期對(duì)象,有新生代就好多了,這時(shí)候就體現(xiàn)出我大 Java 的優(yōu)勢(shì)了)。
由于這種反序列化需要對(duì)象頭和垃圾回收機(jī)制,我們能在 Python 層做改進(jìn)的空間非常小。
Rust 的 Source Maps在調(diào)查發(fā)現(xiàn)問(wèn)題在于 Python 的性能缺陷后,我們決定嘗試 Rust source map 解析器的性能,這是為我們的 CLI 工具編寫的。在將 Rust 解析器應(yīng)用于問(wèn)題很大的 source map 之后,其表明多帶帶使用該庫(kù)進(jìn)行解析可以將處理時(shí)間從 > 20 秒減少到 < 0.5 秒。這意味著即使忽略任何優(yōu)化,只是將 Python 解析器替換為 Rust 解析器就可以緩解我們的性能瓶頸。
我們證明 Rust 確實(shí)更快后,就清理了一些 Sentry 內(nèi)部 API,以便我們可以用新的庫(kù)替換原來(lái)的實(shí)現(xiàn)。這個(gè) Python 庫(kù)命名為 libsourcemap,是我們自己的 Rust source map 的一個(gè)薄包裝。
優(yōu)化結(jié)果部署該庫(kù)后,專門用于 source map 處理的機(jī)器壓力大大降低。
最糟糕的 source map 處理時(shí)間減少到原來(lái)的十分之一。
更重要的是,平均處理時(shí)間減少到? 400 ms。
JavaScript 是我們最受歡迎的項(xiàng)目語(yǔ)言,這種變化達(dá)到了將所有事件的端到端處理時(shí)間減少到? 300 ms。
在 Python 中 嵌入 Rust有很多方法可以暴露 Rust 庫(kù)給 Python。我們選擇將 Rust 代碼編譯成一個(gè) dylib,并提供一些 ol"C 函數(shù),通過(guò) CFFI 和 C 頭文件暴露給 Python。有了 C 語(yǔ)言頭文件,CFFI 生成一些 shim( shim 是一個(gè)小型的函數(shù)庫(kù),用于透明地?cái)r截 API 調(diào)用,修改傳遞的參數(shù)、自身處理操作、或把操作重定向到其他地方),可以調(diào)用 Rust。這樣,libsourcemap 可以打開在運(yùn)行時(shí)從 Rust 生成的動(dòng)態(tài)共享庫(kù)。
這個(gè)過(guò)程有兩個(gè)步驟。第一個(gè)是在 setup.py 運(yùn)行時(shí)配置 CFFI 的構(gòu)建模塊:
在構(gòu)建模塊之后,頭文件通過(guò) C 預(yù)處理器來(lái)處理,以便擴(kuò)展宏( CFFI 本身無(wú)法執(zhí)行的過(guò)程)。此外,這將告訴 CFFI 在哪里放置生成的 shim 模塊。所有完成的之后,加載模塊:
下一步是編寫一些包裝器代碼來(lái)為 Rust 對(duì)象提供一個(gè) Python API,這樣能夠轉(zhuǎn)發(fā)異常。這發(fā)生在兩個(gè)過(guò)程中:首先,確保在 Rust 代碼中,我們盡可能使用結(jié)果對(duì)象。此外,我們需要處理好 panic,以確保他們不會(huì)跨越 DLL 邊界。第二,我們定義了一個(gè)可以存儲(chǔ)錯(cuò)誤信息的幫助結(jié)構(gòu) ; 并將其作為 out 參數(shù)傳遞給可能失敗的函數(shù)。
在 Python 中,我們提供了一個(gè)上下文管理器:
我們有一個(gè)特定錯(cuò)誤類( special_errors)的字典,但如果沒(méi)有找到具體的錯(cuò)誤,將會(huì)拋一個(gè)通用的 SourceMapError。
從那里,我們實(shí)際上可以定義 source map 的基類:
在 Rust 中暴露 C API我們從包含一些導(dǎo)出函數(shù)的 C 頭開始,如何從 Rust 導(dǎo)出它們? 有兩個(gè)工具:特殊的# [no_mangle] 屬性和 std :: panic 模塊 ; 提供了 Rust panic 處理器。我們自己建立了一些 helper 來(lái)處理這個(gè):一個(gè)函數(shù)用來(lái)通知 Python 發(fā)生了一個(gè)異常和兩個(gè)異常處理 helper,一個(gè)通用的,另一個(gè)包裝了返回值。有了這個(gè),包裝方法如下:
boxed_landingpad 的工作方式很簡(jiǎn)單。它調(diào)用閉包,用 panic :: catch_unwind 捕獲 panic,解開結(jié)果,并在原始指針中加上成功值。如果發(fā)生錯(cuò)誤,它會(huì)填充 err_out 并返回一個(gè) NULL 指針。在 lsm_view_free 中,只需要從原始指針重新構(gòu)建。
構(gòu)建擴(kuò)展要實(shí)際構(gòu)建擴(kuò)展,我們必須在 setuptools 中做一些不太優(yōu)雅的事情。幸運(yùn)的是,在這件事上我們沒(méi)有花太多時(shí)間,因?yàn)槲覀円呀?jīng)有一個(gè)類似的工具來(lái)處理。
這個(gè)做法最方便的部分是源代碼用 cargo 編譯,二進(jìn)制安裝最終的 dylib,消除任何最終用戶使用 Rust 工具鏈的需要。
那些做得好,那些沒(méi)做好?我在 Twitter 上被問(wèn)到:“ Rust 會(huì)有什么替代品?”說(shuō)實(shí)話,Rust 很難替代。原因是,除非你想用性能更好的語(yǔ)言重寫整個(gè) Python 組件,否則只能使用本機(jī)擴(kuò)展。在這種情況下,對(duì)語(yǔ)言的要求是相當(dāng)苛刻的:它不能有一個(gè)侵入式運(yùn)行時(shí),不能有一個(gè) GC,并且必須支持 C ABI?,F(xiàn)在,我認(rèn)為適合的語(yǔ)言是 C,C++ 和 Rust。
哪方面工作的好:
結(jié)合 Rust 和 Python 與 CFFI。有一些替代品,鏈接到 libpython,但構(gòu)建更復(fù)雜。
在老一些的 CentOS 版本使用 Docker 來(lái)構(gòu)建可移植的 Linux 容器。雖然這個(gè)過(guò)程是乏味的,然而不同的 Linux 發(fā)興版和內(nèi)核之間的穩(wěn)定性的差異使得 Docker 和 CentOS 成為可接受的構(gòu)建解決方案。
Rust 生態(tài)系統(tǒng)。我們使用 crates.io 的 serde 反序列化和 base64 庫(kù),兩個(gè)庫(kù)工作非常好。此外,mmap 支持使用由社區(qū) memmap 提供的另一庫(kù)。
哪方面工作的不好:
迭代和編譯時(shí)間真的可以更好。我們每次更改字符時(shí)都編譯模塊和頭文件。
setuptools 步驟非常脆弱。我們可能花了更多的時(shí)間來(lái)使 setuptools 工作。幸運(yùn)的是,我們以前做過(guò)一次,所以這次更容易。
雖然 Rust 對(duì)我們的工作幫助很大,毫無(wú)疑問(wèn),有很多需要改進(jìn)。特別是,用于導(dǎo)出 C ABI(并使其對(duì) Python 有用)的基礎(chǔ)設(shè)施應(yīng)該有很大改進(jìn)空間。編譯時(shí)間也不是很長(zhǎng)(譯者的話,不是很長(zhǎng)的意思是可能夠我沏杯茶,懷念 go 的編譯速度)。希望增量編譯將有所幫助。
下一步其實(shí)我們還有更多的改進(jìn)空間。我們可以以更高效的格式啟動(dòng)緩存,比如一組存儲(chǔ)在內(nèi)存中的結(jié)構(gòu)體而不是使用解析 JSON。特別是,如果與文件系統(tǒng)緩存配對(duì),我們幾乎可以完全消除加載的成本,因?yàn)槲覀兤椒至怂饕@可以使用 mmap 非常有效。
鑒于這個(gè)好的結(jié)果,我們很可能會(huì)評(píng)估 Rust 更多在未來(lái)處理一些 CPU 密集型的業(yè)務(wù)。然而,對(duì)于大多數(shù)其他操作,程序花更多的時(shí)間等待 IO。
小結(jié)雖然這個(gè)項(xiàng)目取得了巨大的成功,但是我們只花了很少的時(shí)間來(lái)實(shí)現(xiàn)。它降低了我們的處理時(shí)間,它也將幫助我們水平擴(kuò)展。Rust 一直是這個(gè)工作的完美工具,因?yàn)樗试S我們將昂貴的操作使用本地庫(kù)完成,而且不必使用 C 或 C ++(這不太適合這種復(fù)雜的任務(wù))。雖然很容易在 Rust 中編寫 source map 解析器,但是使用 C / C++ 來(lái)完成的話,代碼更多,且沒(méi)那么有意思。
我們確實(shí)喜歡 Python,并且是許多 Python 開源計(jì)劃的貢獻(xiàn)者。雖然 Python 仍然是我們最喜歡的語(yǔ)言,但我們相信在合適的地方使用合適的語(yǔ)言。Rust 被證明是這項(xiàng)工作的最佳工具,我們很高興看到 Rust 和 Python 將來(lái)會(huì)帶給我們什么。
譯者注:不熟悉 source map 的同學(xué)請(qǐng)看阮一峰的這篇文章 http://www.ruanyifeng.com/blo...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/44207.html
圖示為GPU性能排行榜,我們可以看到所有GPU的原始相關(guān)性能圖表。同時(shí)根據(jù)訓(xùn)練、推理能力由高到低做了排名。我們可以看到,H100 GPU的8位性能與16位性能的優(yōu)化與其他GPU存在巨大差距。針對(duì)大模型訓(xùn)練來(lái)說(shuō),H100和A100有絕對(duì)的優(yōu)勢(shì)首先,從架構(gòu)角度來(lái)看,A100采用了NVIDIA的Ampere架構(gòu),而H100則是基于Hopper架構(gòu)。Ampere架構(gòu)以其高效的圖形處理性能和多任務(wù)處理能力而...
文中關(guān)鍵闡述了pythontime控制模塊時(shí)間格式與結(jié)構(gòu)型時(shí)長(zhǎng)的基本知識(shí),文中為大家介紹得非常詳盡,對(duì)大家學(xué)習(xí)知識(shí)和工作具有很強(qiáng)的參照參考意義,必須的小伙伴可以借鑒一下 time控制模塊 1:簡(jiǎn)述 時(shí)間表示方法的種類 時(shí)間格式 格式化硬盤的時(shí)間也字符串?dāng)?shù)組 結(jié)構(gòu)型時(shí)長(zhǎng) 時(shí)間格式:時(shí)間格式表述的是以1970年1月1日整0點(diǎn)至目前秒偏移,數(shù)據(jù)類型是字符型,主要是用于讓電子計(jì)算機(jī)看得 ...
小編寫這篇文章的主要目的,主要是用來(lái)進(jìn)行繪制折線圖,在繪制的時(shí)候,用到的是Python這門語(yǔ)言,主要應(yīng)用到的技能是Python pyecharts,利用它去進(jìn)行繪制折線圖,下面小編就以案例的形式,去給大家詳細(xì)的做個(gè)介紹?! ∏把浴 ∠嘈庞泻芏嗟男』锇榭戳巳绱硕鄠€(gè)案例之后肯定有所發(fā)現(xiàn),每一個(gè)案例都對(duì)應(yīng)著每一個(gè)配置,如果是官方配置文檔,說(shuō)實(shí)話看起來(lái)真的很難,這樣通過(guò)案例實(shí)現(xiàn)來(lái)解決各種參數(shù)的配置,我...
摘要:壓縮文件從秒到秒的優(yōu)化過(guò)程有一個(gè)需求需要將前端傳過(guò)來(lái)的張照片,然后后端進(jìn)行處理以后壓縮成一個(gè)壓縮包通過(guò)網(wǎng)絡(luò)流傳輸出去。源碼如下使用映射文件開始時(shí)間內(nèi)存中的映射文件打印如下可以看到速度和使用的速度差不多的。 壓縮20M文件從30秒到1秒的優(yōu)化過(guò)程 有一個(gè)需求需要將前端傳過(guò)來(lái)的10張照片,然后后端進(jìn)行處理以后壓縮成一個(gè)壓縮包通過(guò)網(wǎng)絡(luò)流傳輸出去。之前沒(méi)有接觸過(guò)用Java壓縮文件的,所以就直接...
Python的用處還是比較的大的,在工作當(dāng)中,方方面面的都會(huì)遇到使用Python這門技能。那么,怎么實(shí)現(xiàn)圖形之間的轉(zhuǎn)換呢?比如,將我們平常的頭像,轉(zhuǎn)換成為動(dòng)漫風(fēng)格的呢?下面就給大家詳細(xì)解答下?! ∽罱贕ithub上面有看到將頭像轉(zhuǎn)化成動(dòng)漫風(fēng)的項(xiàng)目,但是對(duì)于不少?zèng)]有技術(shù)背景的同學(xué)來(lái)說(shuō)可能就不知道該怎么使用了,小編今天制作了一個(gè)UI界面,大家可以通過(guò)一鍵點(diǎn)擊就實(shí)現(xiàn)頭像照片轉(zhuǎn)化成動(dòng)漫風(fēng)格的功能?!?..
閱讀 5574·2021-11-25 09:43
閱讀 1765·2021-10-27 14:18
閱讀 1124·2021-09-22 16:03
閱讀 1431·2019-08-30 13:19
閱讀 1639·2019-08-30 11:15
閱讀 1782·2019-08-26 14:04
閱讀 3195·2019-08-23 18:40
閱讀 1228·2019-08-23 18:17