go?variant底層原理深入解析
varint
今天本來(lái)在研究 OpenTelemetry 的基準(zhǔn)性能測(cè)試 github.com/zdyj3170101…,測(cè)試不同網(wǎng)絡(luò)協(xié)議:grpc, grpc-stream, http, websocket 在發(fā)送不同大小數(shù)據(jù)包時(shí)消耗 cpu,吞吐 的區(qū)別,由 tigrannajaryan 這位大神所寫。
好奇翻了翻該大神的 github 倉(cāng)庫(kù),發(fā)現(xiàn)了一個(gè)同樣神奇的庫(kù)。
這個(gè)庫(kù)也是基準(zhǔn)的性能測(cè)試,用來(lái)測(cè)試 go 中不同方法實(shí)現(xiàn)的 多類型變量 在消耗 cpu 以及 內(nèi)存上的區(qū)別。
旨在實(shí)現(xiàn)一個(gè)能存儲(chǔ)多類型變量并且具有最小 cpu 消耗以及 內(nèi)存消耗的數(shù)據(jù)結(jié)構(gòu)。
benchmarks
- Interface: 接口
- struct:struct,多個(gè) field 存放不同類型的結(jié)構(gòu)體
- variant:該庫(kù)的時(shí)間
struct
struct 是一個(gè)結(jié)構(gòu)體,typ 表示當(dāng)前結(jié)構(gòu)體的類型,不同的 field 分別存儲(chǔ)不同的類型。
type Variant struct { typ variant.Type bytes []byte str string i int f float64 }
struct 還有兩種不同的類型。
根據(jù)是否返回指針?lè)謩e為 plainstruct 和 ptrstruct。
而 ptrstruct 相比 plainstruct 就多出一次內(nèi)存分配,以及增加 cpu 耗時(shí)(棧上內(nèi)存分配幾個(gè)移位指令就能完成)。
func StringVariant(v string) Variant { return Variant{typ: variant.TypeString, str: v} } ? func StringVariant(v string) *Variant { return &Variant{typ: variant.TypeString, str: v} }
進(jìn)行 benchmark 后發(fā)現(xiàn) plainstruct 已經(jīng) 0 byte 分配了,我也想不出還有其他的優(yōu)化思路。
yangjie05-mac:plainstruct jie.yang05$ go test -bench=. -benchmem plainstruct_test.go plainstruct.go Variant size=64 bytes goos: darwin goarch: amd64 cpu: VirtualApple @ 2.50GHz BenchmarkVariantIntGet-10 1000000000 0.3111 ns/op 0 B/op 0 allocs/op BenchmarkVariantFloat64Get-10 1000000000 0.3117 ns/op 0 B/op 0 allocs/op BenchmarkVariantIntTypeAndGet-10 1000000000 0.3189 ns/op 0 B/op 0 allocs/op BenchmarkVariantStringTypeAndGet-10 141588165 8.435 ns/op 0 B/op 0 allocs/op BenchmarkVariantBytesTypeAndGet-10 140932470 8.465 ns/op 0 B/op 0 allocs/op BenchmarkVariantIntSliceGetAll-10 7293846 165.7 ns/op 640 B/op 1 allocs/op BenchmarkVariantIntSliceTypeAndGetAll-10 7491408 170.6 ns/op 640 B/op 1 allocs/op BenchmarkVariantStringSliceTypeAndGetAll-10 7061575 170.1 ns/op 640 B/op 1 allocs/op ?
variant
一個(gè) variant 由指向真實(shí)數(shù)據(jù)的指針 ptr,一個(gè)緊湊的 lenandtype 同時(shí)表示長(zhǎng)度和類型,這個(gè)數(shù)據(jù)結(jié)構(gòu)還根據(jù)不同位的系統(tǒng)做了優(yōu)化,以及 capOrVal(在slice類型數(shù)據(jù)時(shí),就是 cap,非slice類型數(shù)據(jù)時(shí)就是val )。
- 32位系統(tǒng)下,type 占3位,len 用29位表示
- 64 位系統(tǒng)下,type占3位,len用63位表示。
Variant 設(shè)計(jì)主要是為了同時(shí)滿足存儲(chǔ) float64 和 string 的需求。 因?yàn)?float64 的存在,必須要有一個(gè) int64 類型的字段存儲(chǔ) float64 的值。 而 string 的 len 是int類型的字段,就不需要用int64。
type Variant struct { // Pointer to the slice start for slice-based types. ptr unsafe.Pointer ? // Len and Type fields. // Type uses `typeFieldBitCount` least significant bits, Len uses the rest. // Len is used only for the slice-based types. lenAndType int ? // Capacity for slice-based types, or the value for other types. For Float64Val type // contains the 64 bits of the floating point value. capOrVal int64 }
比如創(chuàng)建一個(gè)string的時(shí)候,ptr 中存放指向數(shù)據(jù)的指針,而lenAndType 中存儲(chǔ)slice的長(zhǎng)度以及 type。 ``
// NewString creates a Variant of TypeString type. func NewString(v string) Variant { hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) if hdr.Len > maxSliceLen { panic("maximum len exceeded") } ? return Variant{ ptr: unsafe.Pointer(hdr.Data), lenAndType: (hdr.Len << typeFieldBitCount) | int(TypeString), } }
為什么 variant 要比 plainstruct 快
分別測(cè)試 variant 和 plainstruct 創(chuàng)建 string 的性能:
func createVariantString() Variant { // 防止編譯優(yōu)化掉? for i := 0; i < 1; i++ { return StringVariant(testutil.StrMagicVal) } return StringVariant("def") } func BenchmarkVariantStringTypeAndGet(b *testing.B) { for i := 0; i < b.N; i++ { v := createVariantString() if v.Type() == variant.TypeString { if v.String() == "" { panic("empty string") } } else { panic("invalid type") } } }
使用 go tool 做性能測(cè)試,并查看plainstruct的profile文件:
go test -o=bin -bench=. -v -test.cpuprofile=cpuprofile plainstruct_test.go plainstruct.go go tool pprof -http=: bin cpuprofile
同理 variant:
go test -o=bin -bench=. -v -test.cpuprofile=cpuprofile variant_test.go variant.go variant_64.go go tool pprof -http=: bin cpuprofile
variant 的匯編:
plainstruct的匯編:
主要區(qū)別還是plainstrutc的指令數(shù)太多,因?yàn)閟truct的字段更多。
variant 可能的優(yōu)化?
variant 其實(shí)這里還有一個(gè)優(yōu)化的方向,就是在 32 位機(jī)器存儲(chǔ) float64 的時(shí)候。 將 float64 拆成兩個(gè) int32,分別用 ptr 和 capOrVal 來(lái)存儲(chǔ)。 這樣在 32位系統(tǒng)下,capOrVal 可以由 int64 變成 int,節(jié)省了 4 個(gè)字節(jié)。
type Variant struct { // Pointer to the slice start for slice-based types. ptr unsafe.Pointer // Len and Type fields. // Type uses `typeFieldBitCount` least significant bits, Len uses the rest. // Len is used only for the slice-based types. lenAndType int capOrVal int }
以上就是go variant原理深入解析的詳細(xì)內(nèi)容,更多關(guān)于go variant的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
GO語(yǔ)言類型轉(zhuǎn)換和類型斷言實(shí)例分析
這篇文章主要介紹了GO語(yǔ)言類型轉(zhuǎn)換和類型斷言,以實(shí)例形式詳細(xì)分析了類型轉(zhuǎn)換和類型斷言的概念與使用技巧,需要的朋友可以參考下2015-01-01Golang map實(shí)踐及實(shí)現(xiàn)原理解析
這篇文章主要介紹了Golang map實(shí)踐以及實(shí)現(xiàn)原理,Go 語(yǔ)言中,通過(guò)哈希查找表實(shí)現(xiàn) map,用鏈表法解決哈希沖突,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06go編程中g(shù)o-sql-driver的離奇bug解決記錄分析
這篇文章主要為大家介紹了go編程中g(shù)o-sql-driver的離奇bug解決記錄分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05在Go語(yǔ)言中實(shí)現(xiàn)DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)例探究
本文將詳細(xì)探討在Go項(xiàng)目中實(shí)現(xiàn)DDD的核心概念、實(shí)踐方法和實(shí)例代碼,包括定義領(lǐng)域模型、創(chuàng)建倉(cāng)庫(kù)、實(shí)現(xiàn)服務(wù)層和應(yīng)用層,旨在提供一份全面的Go DDD實(shí)施指南2024-01-01Golang庫(kù)插件注冊(cè)加載機(jī)制的問(wèn)題
這篇文章主要介紹了Golang庫(kù)插件注冊(cè)加載機(jī)制,這里說(shuō)的插件并不是指的golang原生的可以在buildmode中加載指定so文件的那種加載機(jī)制,需要的朋友可以參考下2022-03-03golang實(shí)現(xiàn)sql結(jié)果集以json格式輸出的方法
這篇文章主要介紹了golang實(shí)現(xiàn)sql結(jié)果集以json格式輸出的方法,涉及Go語(yǔ)言針對(duì)sql結(jié)果集的遍歷、轉(zhuǎn)換及json格式相關(guān)操作技巧,需要的朋友可以參考下2017-03-03一篇文章說(shuō)清楚?go?get?使用私有庫(kù)的方法
這篇文章主要介紹了go?get?如何使用私有庫(kù),本文會(huì)明確指出Git?、golang的配置項(xiàng),附送TortoiseGit?+?Git混合配置,需要的朋友可以參考下2022-09-09