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 這位大神所寫(xiě)。
好奇翻了翻該大神的 github 倉(cāng)庫(kù),發(fā)現(xiàn)了一個(gè)同樣神奇的庫(kù)。
這個(gè)庫(kù)也是基準(zhǔn)的性能測(cè)試,用來(lái)測(cè)試 go 中不同方法實(shí)現(xiàn)的 多類(lèi)型變量 在消耗 cpu 以及 內(nèi)存上的區(qū)別。
旨在實(shí)現(xiàn)一個(gè)能存儲(chǔ)多類(lèi)型變量并且具有最小 cpu 消耗以及 內(nèi)存消耗的數(shù)據(jù)結(jié)構(gòu)。
benchmarks
- Interface: 接口
- struct:struct,多個(gè) field 存放不同類(lèi)型的結(jié)構(gòu)體
- variant:該庫(kù)的時(shí)間

struct
struct 是一個(gè)結(jié)構(gòu)體,typ 表示當(dāng)前結(jié)構(gòu)體的類(lèi)型,不同的 field 分別存儲(chǔ)不同的類(lèi)型。
type Variant struct {
typ variant.Type
bytes []byte
str string
i int
f float64
}
struct 還有兩種不同的類(lèi)型。
根據(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)度和類(lèi)型,這個(gè)數(shù)據(jù)結(jié)構(gòu)還根據(jù)不同位的系統(tǒng)做了優(yōu)化,以及 capOrVal(在slice類(lèi)型數(shù)據(jù)時(shí),就是 cap,非slice類(lèi)型數(shù)據(jù)時(shí)就是val )。
- 32位系統(tǒng)下,type 占3位,len 用29位表示
- 64 位系統(tǒng)下,type占3位,len用63位表示。
Variant 設(shè)計(jì)主要是為了同時(shí)滿(mǎn)足存儲(chǔ) float64 和 string 的需求。 因?yàn)?float64 的存在,必須要有一個(gè) int64 類(lèi)型的字段存儲(chǔ) float64 的值。 而 string 的 len 是int類(lèi)型的字段,就不需要用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)文章
詳解Golang如何監(jiān)聽(tīng)某個(gè)函數(shù)的開(kāi)始執(zhí)行和執(zhí)行結(jié)束
這篇文章主要為大家詳細(xì)介紹了Golang如何監(jiān)聽(tīng)某個(gè)函數(shù)的開(kāi)始執(zhí)行和執(zhí)行結(jié)束,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2024-02-02
Golang?Template實(shí)現(xiàn)自定義函數(shù)的操作指南
這篇文章主要為大家詳細(xì)介紹了Golang如何利用Template實(shí)現(xiàn)自定義函數(shù)的操作,文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02
golang框架gin的日志處理和zap lumberjack日志使用方式
這篇文章主要介紹了golang框架gin的日志處理和zap lumberjack日志使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
Go語(yǔ)言學(xué)習(xí)網(wǎng)絡(luò)編程與Http教程示例
這篇文章主要為大家介紹了Go語(yǔ)言學(xué)習(xí)網(wǎng)絡(luò)編程與Http教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Go語(yǔ)言實(shí)現(xiàn)聊天小工具的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)聊天小工具,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
Go中函數(shù)的使用細(xì)節(jié)與注意事項(xiàng)詳解
在Go語(yǔ)言中函數(shù)可是一等的(first-class)公民,函數(shù)類(lèi)型也是一等的數(shù)據(jù)類(lèi)型,下面這篇文章主要給大家介紹了關(guān)于Go中函數(shù)的使用細(xì)節(jié)與注意事項(xiàng)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
使用Go語(yǔ)言實(shí)現(xiàn)并發(fā)處理CSV文件到數(shù)據(jù)庫(kù)
Go?語(yǔ)言的?goroutine?和通道(channel)非常適合用來(lái)并發(fā)地處理數(shù)據(jù),本文將通過(guò)簡(jiǎn)單示例介紹一下如何使用Go語(yǔ)言并發(fā)地處理?CSV?文件并將數(shù)據(jù)插入到數(shù)據(jù)庫(kù)中,感興趣的可以了解下2025-01-01
Go語(yǔ)言中?Print?Printf和Println?的區(qū)別解析
這篇文章主要介紹了Go語(yǔ)言中?Print?Printf和Println?的區(qū)別,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03

