偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

ROG:高性能 Go 實(shí)現(xiàn)

開(kāi)發(fā)
本文根據(jù)字節(jié)跳動(dòng)服務(wù)框架團(tuán)隊(duì)研發(fā)工程師在 CloudWeGo 技術(shù)沙龍暨三周年慶典中演講內(nèi)容《ROG——高性能 Go 實(shí)現(xiàn)》整理。

ROG 之緣起

ROG 的誕生是因?yàn)槲覀円徊糠謽I(yè)務(wù)使用 Rust 重寫之后,獲得了非常好的收益,比如 AVG、CPU、MEM、P99,這些數(shù)據(jù)表現(xiàn)非常好,大約節(jié)省了接近 50%的 CPU,內(nèi)存大大降低。這個(gè)性能數(shù)據(jù)讓人眼紅,因此團(tuán)隊(duì)考慮既然 Rust 有這么好的性能,我們有沒(méi)有辦法提升一下用戶在 Go 上的性能?

圖片

在和一些用戶的對(duì)接中我們發(fā)現(xiàn),讓用戶把 Go 業(yè)務(wù)通過(guò) Rust 重寫,難度其實(shí)非常大。很多用戶會(huì)抱怨 Rust 的一些問(wèn)題讓他們很痛苦,比如,Rust 生命周期太復(fù)雜,泛型系統(tǒng)太復(fù)雜,報(bào)錯(cuò)看不懂,編程速度慢等等。因?yàn)檫@一系列問(wèn)題,所以讓用戶把原來(lái)的 Go 項(xiàng)目通過(guò) Rust 重寫,對(duì)于用戶來(lái)說(shuō)是很難推動(dòng)的事情。

于是,我們就有了一個(gè)大膽的想法,如果我們可以像使用 Rust 那樣的編譯技術(shù)去生成性能更好的可執(zhí)行文件,同時(shí)使用 Rust 重寫 Go 的 Runtime 和 GC 這兩個(gè)核心組件,再通過(guò)幾乎零開(kāi)銷的 FFI(Foreign Function Interface) 方式來(lái)支持 Rust 和 Go 之間的互調(diào),是不是可以讓用戶 Go 的源碼也能達(dá)到接近 Rust 的性能。這就是我們的初衷,因此有了 ROG 這個(gè)項(xiàng)目。

ROG 進(jìn)展

我們目前測(cè)試了一些簡(jiǎn)單的場(chǎng)景,比如快排和二分、Simple Lisp。這些都是通過(guò) time 命令來(lái)計(jì)算兩個(gè)二進(jìn)制文件執(zhí)行所需要的時(shí)間。目前在快排、二分上,Go 的執(zhí)行需要 5.97s,ROG 的執(zhí)行需要 4.12s,在 Simple Lisp 這個(gè)項(xiàng)目上,Go 需要 8.17s,ROG 執(zhí)行只需要 7.09s。

圖片

從以上幾個(gè)基本數(shù)字來(lái)看,在一些簡(jiǎn)單的場(chǎng)景下,ROG 會(huì)比 Go 性能好很多。但這只是一些非常簡(jiǎn)單的 case。如果面對(duì)一些非常復(fù)雜的 case 呢?比如在復(fù)雜的微服務(wù)場(chǎng)景下,ROG 會(huì)有怎樣的性能領(lǐng)先?

ROG 在上個(gè)季度剛好能夠支撐 Kitex Benchmark 跑起來(lái),目前我們完成了一次壓測(cè)。

圖片

我們使用 Kitex 官方的 Benchmark 工具完成了簡(jiǎn)單的 RPC 調(diào)用測(cè)試(https://github.com/cloudwego/kitex-benchmark)。

目前,我們只測(cè)試了連接數(shù) 100,測(cè)試包大小 1024kb 的體積。在這個(gè)測(cè)試中,Go 的 QPS 可以達(dá)到 27W,ROG 28W。雖然 ROG 的 QPS 只比 Go 領(lǐng)先了一點(diǎn),但是 P99 上有很大的提升。我們?cè)跍y(cè)試過(guò)程中發(fā)現(xiàn)了 ROG 還有很多可以挖掘能力,只是還需要進(jìn)一步優(yōu)化。

架構(gòu)設(shè)計(jì)

通過(guò)剛才幾個(gè)性能場(chǎng)景測(cè)試,我們發(fā)現(xiàn) ROG 相比 Go 在不同的場(chǎng)景下,多多少少有一些領(lǐng)先。但是為什么 ROG 相比于 Go 會(huì)有這樣的領(lǐng)先呢?早期我們其實(shí)也經(jīng)歷過(guò) ROG 測(cè)試結(jié)果比 Go 還差 50%的狀態(tài)。所以想先給大家介紹一下我們的設(shè)計(jì)架構(gòu)。

圖片

從圖中可以看出,首先會(huì)有一個(gè) ROG 的前端來(lái)處理用戶的 Go 源碼,在前端經(jīng)歷 Parser 解析后生成 AST(Abstract Syntax Tree),做符號(hào)解析,每個(gè)函數(shù),每個(gè)類型的符號(hào)。然后進(jìn)行類型檢查,分析出函數(shù)的簽名以及每個(gè)變量的類型。這是一套非常常見(jiàn)的前端處理流程。

在經(jīng)歷這個(gè)過(guò)程之后,會(huì)產(chǎn)生一個(gè)中間語(yǔ)言叫做 MIR(Rust's Mid-level Intermediate Representation),之后會(huì)基于 MIR 去做一些前端時(shí)的優(yōu)化,比如編譯時(shí)計(jì)算、常量傳播計(jì)算、逃逸分析(能夠分析出哪些變量應(yīng)該被逃逸到堆上去)、Inliner、SROA,以及對(duì)于特定 Go 函數(shù)的優(yōu)化。

在這些優(yōu)化算法處理之后,會(huì)生成一份 LLVM IR(Intermediate Representation),之后把它交給 ROG 后端。ROG 后端是我們自己魔改的一個(gè) LLVM 版本。在 LLVM codegen 階段我們給每個(gè)函數(shù)插入了一些對(duì)應(yīng)的 Stack Check 以及對(duì)應(yīng)的 STW(Stop The World) Checkpoint 指令,同時(shí)生成相應(yīng)的 GC Barrier。

優(yōu)化好之后就生成一份比較高質(zhì)量的二進(jìn)制代碼了。這是對(duì)于 Go 語(yǔ)言的處理,而對(duì)于 Go 的 Runtime & GC 這部分,我們基本上完全是重寫的。通過(guò) Rust 重寫之后,我們把這些代碼通過(guò)自己維護(hù)的一個(gè) Rust 版本去構(gòu)建、打包好,調(diào)成對(duì)應(yīng)的 LLVM 文件,最后和用戶的 Go 代碼連接起來(lái),形成一個(gè)最終的二進(jìn)制文件。這就是我們的編譯流程。

收益來(lái)源

這個(gè)編譯架構(gòu)為什么相比 Go 或多或少有些性能優(yōu)化呢?有哪些領(lǐng)先點(diǎn)?

其實(shí)領(lǐng)先點(diǎn)主要來(lái)源于三個(gè)部分。

圖片

第一部分,編譯優(yōu)化。因?yàn)?ROG 利用了 LLVM 積累多年的編譯優(yōu)化算法,能夠生成一些性能更好的代碼,而 Go 的編譯優(yōu)化會(huì)為編譯速度做出一定犧牲。

第二部分,ROG 提供了跨語(yǔ)言 LTO(Link Time Optimization) 以及 FFI,通過(guò)幾乎零開(kāi)銷的方式調(diào)用 Rust 提供的方法,因此在一些需要更高性能的場(chǎng)景,用戶可以使用 Rust 開(kāi)發(fā),由 ROG 進(jìn)行編譯并進(jìn)行調(diào)用。而 Go 對(duì)于 FFI 會(huì)使用 CGO,并且 CGO 會(huì)存在一些 overhead。

第三部分,Runtime & GC。ROG 完全使用 Rust 重寫,再通過(guò)上面提供的 FFI 來(lái)保證調(diào)用的性能,而 Go 的 Runtime & GC 則是完全使用 Go 原生實(shí)現(xiàn)的。單純從語(yǔ)言的表達(dá)能力上限來(lái)說(shuō),Go 遠(yuǎn)不如 Rust,所以如果我們通過(guò) Rust 來(lái)重寫 Runtime & GC 這兩部分組件,理論上會(huì)比 Go 擁有更好的性能。

面臨的挑戰(zhàn)

介紹完性能來(lái)源之后,可能很多人會(huì)有疑問(wèn),貌似我們的主要性能受益都是來(lái)自于 LLVM。LLVM 本身優(yōu)化已經(jīng)做得很好了,我們做的是不是就是非常簡(jiǎn)單地把一個(gè) Go 源碼翻譯到 LLVM 就行了呢?

其實(shí)整個(gè)事情并沒(méi)有那么簡(jiǎn)單,在這一年里,我們踩過(guò)非常多的坑。以下舉幾個(gè)簡(jiǎn)單的例子。

Go Runtime

如果大家之前了解過(guò) TinyGo,就會(huì)發(fā)現(xiàn) TinyGo 的思路和 ROG 非常接近——TinyGo 也是把 Go 的源碼給翻譯到 LLVM。我們可以回想下在使用 TinyGo 的時(shí)候遇到過(guò)什么問(wèn)題。

首先,TinyGo 需要用戶手動(dòng)通過(guò) runtime.Gosched 這個(gè)函數(shù)來(lái)進(jìn)行協(xié)作調(diào)度,所以它對(duì)用戶代碼是有影響的。如果用戶沒(méi)有在關(guān)鍵的地方去插入這個(gè)函數(shù)調(diào)度,會(huì)對(duì)它的調(diào)度產(chǎn)生影響。另外,TinyGo 本身也不支持多線程,并且缺少相應(yīng)的 channel timer reflect 等 lib 的支持。

而 ROG 把這些問(wèn)題都解決了,ROG 會(huì)在編譯階段插入代碼,完成協(xié)作式調(diào)度,并且 ROG 設(shè)計(jì)的本身也是為了高性能,所以自然會(huì)對(duì)多線程進(jìn)行支持,并且 ROG 對(duì)于 channel timer reflect 全部都重寫。對(duì)于我們來(lái)說(shuō),解決 TinyGo 的不足的過(guò)程也相當(dāng)艱難,畢竟重寫整個(gè) Runtime & GC 等是一個(gè)非常大的工作量。

圖片

Safety FFI

假設(shè)如果我們要在 Go 提供 FFI,當(dāng)用戶寫出這樣的代碼會(huì)發(fā)生什么事情?

圖片

左邊這張圖是用戶寫的一份 Go 代碼,里面有函數(shù)。rog_test(a *int32) 這個(gè)函數(shù)可能就是 FFI 提供的一個(gè)外部函數(shù)。如果用戶直接去調(diào)用這個(gè)外部函數(shù),而 rog test 本身是由 Rust 實(shí)現(xiàn)的,如右圖,當(dāng)我們寫出這樣的代碼的時(shí)候,會(huì)發(fā)生什么事情?

因?yàn)?nbsp;rust_tup 被 Rust Allocator 管理,Go GC 無(wú)法掃描到這個(gè)變量,所以這個(gè)變量 a 也無(wú)法被 GC 掃描到,而 “a” 這個(gè)變量是被 Go 的 Allocator 管理的,所以如果  a 無(wú)法被 GC 掃描到,那么 a 就會(huì)被 free 掉。但是這個(gè)時(shí)候, rust_tup 仍然會(huì)持有變量 a 的指針,在 Go 那邊相當(dāng)于是一個(gè)對(duì)外內(nèi)存引用了 Go 的一個(gè)對(duì)象,但是因?yàn)?Go 掃不到這個(gè)對(duì)象,所以這個(gè)對(duì)象就被 free 掉了,但是對(duì)外內(nèi)存仍然引用這個(gè)指針。

當(dāng)我們提供 FFI 的時(shí)候,很有可能會(huì)面臨這樣的情況。這種情況該怎么處理?在 ROG 這邊,我們就會(huì)通過(guò)一個(gè)模改的 Rust 編譯器,提供一個(gè) Managed Chekcer 去限制用戶寫出這樣的代碼,在編譯器階段保證用戶不會(huì)寫出這樣的代碼,保證 FFI 的安全性。這是 ROG 解決這個(gè)問(wèn)題的思路。

Roadmap & 未來(lái)規(guī)劃

CGO

目前 ROG 雖然能跑過(guò) Kitex Benchmark,并在內(nèi)部一些服務(wù)上做了測(cè)試,但它仍有很多功能需要改進(jìn),比如 CGO。CGO 是 Go 語(yǔ)言用來(lái)提供 FFI 的一種方案,但 ROG 的 FFI 是通過(guò)一種非常簡(jiǎn)單粗暴的方式提供的。目前 ROG 的 FFI 需要用戶手工去標(biāo)記 ROG,寫上 rog:linkname 標(biāo)記。這樣我們?cè)阪溄訒r(shí)才能鏈接上對(duì)應(yīng)的符號(hào)。而 CGO 可以讓用戶簡(jiǎn)單的直接在 Go 文件的一個(gè)注釋里寫上 C 代碼 import C,通過(guò) import c 這個(gè) package 來(lái)進(jìn)行調(diào)用。

從 FFI 來(lái)說(shuō),CGO 會(huì)比現(xiàn)在的 ROG 方便很多,而且已有很多的開(kāi)源庫(kù),以及字節(jié)內(nèi)部一些服務(wù),他們也在使用 CGO。我們?cè)谖磥?lái)會(huì)支持 CGO,兼容 CGO 的表達(dá)方式,提供 ROG 需要的 FFI,生成 ROG 需要的 FFI 代碼進(jìn)行調(diào)用。

宏/編譯器生成代碼

Rust 宏在我看來(lái)是一個(gè)非常強(qiáng)的功能,因?yàn)?Rust 宏可以簡(jiǎn)單地在每個(gè) Rust 進(jìn)行標(biāo)記,申明這個(gè)結(jié)構(gòu)可以提供 Serialize(序列化)和 Deserialize(反序列化)這兩種方法。這樣就可以在編譯時(shí)為它生成序列化和反序列化的代碼,直接進(jìn)行調(diào)用,而不需要像 Go 原始的 JSON,它有反射開(kāi)銷。而這種反射開(kāi)銷在需要高性能的序列化場(chǎng)景會(huì)有很大的性能開(kāi)銷。為了解決 Go 的反射開(kāi)銷,sonic 做出了 JIT 方案,而 JIT 對(duì)開(kāi)發(fā) sonic 的開(kāi)發(fā)者來(lái)說(shuō),負(fù)擔(dān)是非常大的。

圖片

那么如果我們可以把 Rust 宏的理念引入到 ROG 中,會(huì)有什么樣的體驗(yàn)?

首先,更好的開(kāi)發(fā)體驗(yàn)。以 Kitex 舉例,我們可以直接在編譯時(shí),通過(guò)宏為每個(gè) IDL 生成 clint 的代碼,這樣就不需要用戶去手動(dòng)調(diào)用一些 main 去生成。

其次,更高效的序列化。像 JSON 這種序列化,我們可以通過(guò)類似 Rust 宏的方式在編譯時(shí)生成好序列化和反序列化所需要的代碼,直接調(diào)用,這樣就可以省掉反射的開(kāi)銷。

圖片

關(guān)于宏帶來(lái)的案例,我們還在繼續(xù)探索中,之后我們會(huì)基于宏做一些更好更方便的嘗試。這也是我們對(duì)于宏的規(guī)劃。但是不得不提到的是,宏的出現(xiàn)會(huì)對(duì) Go 本身有一定的影響,因此可能只會(huì)通過(guò)注釋的方式去提供,保證對(duì) Go 語(yǔ)法的兼容性;并且只會(huì)在 JSON 等序列化這些地方進(jìn)行一些替換,保證用戶的開(kāi)發(fā)體驗(yàn)不會(huì)受到影響。

開(kāi)源

ROG 未來(lái)肯定會(huì)進(jìn)行開(kāi)源,并且貢獻(xiàn)到社區(qū)。

目前我們的想法是 2024 年先在公司內(nèi)部完成一些業(yè)務(wù)的試用,能夠穩(wěn)定地上生態(tài)環(huán)境,并且能夠取得一定的收益。在這些都穩(wěn)定并且處理好 Go 本身大部分特性問(wèn)題之后,才會(huì)將其開(kāi)源。因此如果順利,最早可能會(huì)需要等到 2025 年的第二季度才會(huì)去準(zhǔn)備開(kāi)源工作。

責(zé)任編輯:龐桂玉 來(lái)源: 字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)
相關(guān)推薦

2022-03-21 14:13:22

Go語(yǔ)言編程

2023-03-10 09:11:52

高性能Go堆棧

2021-08-13 09:06:52

Go高性能優(yōu)化

2021-05-27 10:02:57

Go緩存數(shù)據(jù)

2025-01-13 13:00:00

Go網(wǎng)絡(luò)框架nbio

2023-12-26 00:58:53

Web應(yīng)用Go語(yǔ)言

2025-02-05 12:09:12

2024-04-28 10:17:30

gnetGo語(yǔ)言

2024-07-31 08:31:13

2019-04-08 10:09:04

CPU緩存高性能

2025-10-09 03:00:00

2023-12-01 07:06:14

Go命令行性能

2023-12-14 08:01:08

事件管理器Go

2024-07-05 09:41:42

2024-01-29 08:26:13

Span高性能數(shù)組數(shù)據(jù)結(jié)構(gòu)

2021-08-12 16:42:09

WireGuardWindows內(nèi)核NT

2011-09-23 13:07:32

Platform

2024-02-26 07:43:10

大語(yǔ)言模型LLM推理框架

2021-07-27 16:01:29

高并發(fā)定時(shí)器高性能

2024-07-30 09:02:15

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)