欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言中節(jié)省內(nèi)存技巧方法示例

 更新時間:2023年01月06日 14:24:45   作者:nil  
這篇文章主要為大家介紹了Go語言中節(jié)省內(nèi)存技巧方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

GO雖然不消耗大量內(nèi)存,但是仍有一些小技巧可以節(jié)省內(nèi)存,良好的編碼習(xí)慣是每一個程序員都應(yīng)該具備的素質(zhì)。

預(yù)先分配切片

數(shù)組是具有連續(xù)內(nèi)存的相同類型的集合。數(shù)組類型定義時要指定長度和元素類型。

因為數(shù)組的長度是它們類型的一部分,數(shù)組的主要問題是它們大小固定,不能調(diào)整。

與數(shù)組類型不同,切片類型無需指定長度。切片的聲明方式與數(shù)組相同,但沒有數(shù)量元素。

切片是數(shù)組的包裝器,它們不擁有任何數(shù)據(jù)——它們是對數(shù)組的引用。它們由指向數(shù)組的指針、長度及其容量(底層數(shù)組中的元素數(shù))組成。

當(dāng)您向沒有足夠容量的切片添加一個新值時 - 會創(chuàng)建一個具有更大容量的新數(shù)組,并將當(dāng)前數(shù)組中的值復(fù)制到新數(shù)組中。這會導(dǎo)致不必要的內(nèi)存分配和 CPU 周期。

為了更好地理解這一點,讓我們看一下以下代碼段:

func main() {
	var ints []int
	fmt.Printf("Address: %p, Length: %d, Capacity: %d, Values: %v\n", ints, len(ints), cap(ints), ints)
	for i := 0; i < 5; i++ {
		ints = append(ints, i)
		fmt.Printf("Address: %p, Length: %d, Capacity: %d, Values: %v\n", ints, len(ints), cap(ints), ints)
	}
}

結(jié)果

Address: 0x0, Length: 0, Capacity: 0, Values: []
Address: 0xc0000160d0, Length: 1, Capacity: 1, Values: [0]
Address: 0xc0000160e0, Length: 2, Capacity: 2, Values: [0 1]
Address: 0xc000020100, Length: 3, Capacity: 4, Values: [0 1 2]
Address: 0xc000020100, Length: 4, Capacity: 4, Values: [0 1 2 3]
Address: 0xc00001a180, Length: 5, Capacity: 8, Values: [0 1 2 3 4]

可以看到第一次聲明數(shù)組var ints []int的時候,是不給它分配內(nèi)存的,內(nèi)存地址為0,大小和容量也都是0 后面每次擴容都是2的倍數(shù),并且每次擴容內(nèi)存地址都發(fā)生了改變。

當(dāng)容量<1024 時會漲為之前的 2 倍,當(dāng)容量>=1024時會以 1.25 倍增長。從 Go 1.18 開始,這已經(jīng)變得更加線性

func BenchmarkPreallocAssign(b *testing.B) {
	ints := make([]int, b.N)
	for i := 0; i < b.N; i++ {
		ints[i] = i
	}
}
func BenchmarkAppend(b *testing.B) {
	var ints []int
	for i := 0; i < b.N; i++ {
		ints = append(ints, i)
	}
}

結(jié)果如下

goos: darwin
goarch: amd64
pkg: mygo
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkPreallocAssign-12      321257311                3.609 ns/op           8 B/op          0 allocs/op
BenchmarkAppend-12              183322678               12.37 ns/op           42 B/op          0 allocs/op
PASS
ok      mygo    6.236s

由上述基準(zhǔn),我們可以得出結(jié)論,將值分配給預(yù)分配的切片和將值追加到切片之間是存在很大差異的。預(yù)先分配大小可以提速3倍多,而且內(nèi)存分配也更小。

結(jié)構(gòu)體中的字段順序

以下面結(jié)構(gòu)體為例

type Post struct {
    IsDraft     bool      // 1 byte
    Title       string    // 16 bytes
    ID          int64     // 8 bytes
    Description string    // 16 bytes
    IsDeleted   bool      // 1 byte
    Author      string    // 16 bytes
    CreatedAt   time.Time // 24 bytes
}
func main(){
    p := Post{}
    fmt.Println(unsafe.Sizeof(p))
}

上述的輸出為 96 字節(jié),而所有字段相加為 82 字節(jié)。那額外的 14 個字節(jié)是來自哪里呢?

現(xiàn)代 64 位 CPU 以 64 位(8 字節(jié))的塊獲取數(shù)據(jù)

第一個周期占用 8 個字節(jié),拉取“IsDraft”字段占用了 1 個字節(jié)并且產(chǎn)生 7 個未使用字節(jié)。它不能占用“一半”的字段。

第二個和第三個周期取 Title 字符串,第四個周期取 ID,依此類推。到取 IsDeleted 字段時,它使用 1 個字節(jié)并有 7 個字節(jié)未使用。

對內(nèi)存節(jié)省的關(guān)鍵是按字段占用大小從上到下對字段進行排序。對上述結(jié)構(gòu)進行排序,大小可減少到 88 個字節(jié)。最后兩個字段 IsDraft 和 IsDeleted 被放在同一個塊中,從而將未使用的字節(jié)數(shù)從 14 (2x7) 減少到 6 (1 x 6),在此過程中節(jié)省了 8 個字節(jié)。

type Post struct {
    CreatedAt   time.Time // 24 bytes
    Title       string    // 16 bytes
    Description string    // 16 bytes
    Author      string    // 16 bytes
    ID          int64     // 8 bytes
    IsDraft     bool      // 1 byte
    IsDeleted   bool      // 1 byte
}
func main(){
    p := Post{}
    fmt.Println(unsafe.Sizeof(p))
}

上述的輸出為 88 字節(jié)

極端情況

type Post struct {
	IsDraft  bool  // 1 byte
	I64      int64 // 8 bytes
	IsDraft1 bool  // 1 byte
	I641     int64 // 8 bytes
	IsDraft2 bool  // 1 byte
	I642     int64 // 8 bytes
	IsDraft3 bool  // 1 byte
	I643     int64 // 8 bytes
	IsDraft4 bool  // 1 byte
	I644     int64 // 8 bytes
	IsDraft5 bool  // 1 byte
	I645     int64 // 8 bytes
	IsDraft6 bool  // 1 byte
	I646     int64 // 8 bytes
	IsDraft7 bool  // 1 byte
	I647     int64 // 8 bytes
}
type Post1 struct {
	IsDraft  bool  // 1 byte
	IsDraft1 bool  // 1 byte
	IsDraft2 bool  // 1 byte
	IsDraft3 bool  // 1 byte
	IsDraft4 bool  // 1 byte
	IsDraft5 bool  // 1 byte
	IsDraft6 bool  // 1 byte
	IsDraft7 bool  // 1 byte
	I64      int64 // 8 bytes
	I641     int64 // 8 bytes
	I642     int64 // 8 bytes
	I643     int64 // 8 bytes
	I644     int64 // 8 bytes
	I645     int64 // 8 bytes
	I646     int64 // 8 bytes
	I647     int64 // 8 bytes
}

第一個結(jié)構(gòu)體占用128字節(jié),第二個結(jié)構(gòu)體占用72字節(jié)。節(jié)省空間:(128-72)/129=43.75%.

在 64 位架構(gòu)上占用小于 8 字節(jié)的 Go 類型:

  • bool: 1 個字節(jié)
  • int8/uint8: 1 個字節(jié)
  • int16/uint16: 2 個字節(jié)
  • int32/uint32/rune: 4 個字節(jié)
  • float32: 4 個字節(jié)
  • byte: 1 個字節(jié)

使用 map[string]struct{} 而不是 map[string]bool

Go 沒有內(nèi)置的集合,通常使用 map[string]bool{} 表示集合。盡管它更具可讀性(這非常重要),但將其作為一個集合使用是錯誤的,因為它具有兩種狀態(tài)(假/真)并且與空結(jié)構(gòu)體相比使用了額外的內(nèi)存。

空結(jié)構(gòu)體 (struct{}) 是沒有額外字段的結(jié)構(gòu)類型,占用零字節(jié)的存儲空間。

func BenchmarkBool(b *testing.B) {
	m := make(map[uint]bool)
	for i := uint(0); i < 100_000_000; i++ {
		m[i] = true
	}
}
func BenchmarkEmptyStruct(b *testing.B) {
	m := make(map[uint]struct{})
	for i := uint(0); i < 100_000_000; i++ {
		m[i] = struct{}{}
	}
}

結(jié)果

goos: darwin
goarch: amd64
pkg: mygo
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkBool-12                       1        24052439603 ns/op       3766222824 B/op  3902813 allocs/op
BenchmarkEmptyStruct-12                1        22450213018 ns/op       3418648448 B/op  3903556 allocs/op
PASS
ok      mygo    46.937s

可以看到執(zhí)行速度提升了一些,但是效果不太明顯。

使用bool值有個好處是查找的時候更方便,從map中取值只需要判斷一個值就行了,而使用空結(jié)構(gòu)體則需要判斷第二個值

m := make(map[string]bool{})
if m["key"]{
 // Do something
}
v := make(map[string]struct{}{})
if _, ok := v["key"]; ok{
    // Do something
}

參考

【1】Go 中簡單的內(nèi)存節(jié)省技巧

【2】Easy memory-saving tricks in Go

以上就是Go語言中節(jié)省內(nèi)存技巧方法示例的詳細內(nèi)容,更多關(guān)于Go語言節(jié)省內(nèi)存技巧的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go1.18?新特性之多模塊Multi-Module工作區(qū)模式

    Go1.18?新特性之多模塊Multi-Module工作區(qū)模式

    這篇文章主要介紹了Go1.18?新特性之多模塊Multi-Module工作區(qū)模式,在 Go 1.18之前,建議使用依賴模塊中的 replace 指令來處理這個問題,從 Go 1.18開始引入了一種同時處理多個模塊的新方法,通過案例給大家詳細介紹,感興趣的朋友一起看看吧
    2022-04-04
  • golang開發(fā)中channel使用

    golang開發(fā)中channel使用

    channel[通道]是golang的一種重要特性,正是因為channel的存在才使得golang不同于其它語言。這篇文章主要介紹了golang開發(fā)中channel使用,需要的朋友可以參考下
    2020-09-09
  • Go語言中內(nèi)存管理逃逸分析詳解

    Go語言中內(nèi)存管理逃逸分析詳解

    所謂的逃逸分析(Escape?analysis)是指由編譯器決定內(nèi)存分配的位置嗎不需要程序員指定。本文就來和大家簡單分析一下Go語言中內(nèi)存管理逃逸吧
    2023-03-03
  • 使用Go語言實現(xiàn)一個簡單的無界資源池

    使用Go語言實現(xiàn)一個簡單的無界資源池

    本文我們希望通過go語言實現(xiàn)一個簡單的資源池,而這個資源池的資源包括但不限于數(shù)據(jù)庫連接池,線程池,協(xié)程池,網(wǎng)絡(luò)連接池,只要這些資源實現(xiàn)我們指定的關(guān)閉方法,則都可以通過我們封裝的資源池進行統(tǒng)一管理,文中通過代碼示例給大家介紹的非常詳細,需要的朋友可以參考下
    2024-05-05
  • go語言實現(xiàn)Elasticsearches批量修改查詢及發(fā)送MQ操作示例

    go語言實現(xiàn)Elasticsearches批量修改查詢及發(fā)送MQ操作示例

    這篇文章主要為大家介紹了go語言實現(xiàn)Elasticsearches批量修改查詢及發(fā)送MQ操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-04-04
  • Go語言實現(xiàn)單端口轉(zhuǎn)發(fā)到多個端口

    Go語言實現(xiàn)單端口轉(zhuǎn)發(fā)到多個端口

    這篇文章主要為大家詳細介紹了Go語言實現(xiàn)單端口轉(zhuǎn)發(fā)到多個端口,文中的示例代碼講解詳細,具有一定的參考價值,對大家的學(xué)習(xí)或工作有一定的幫助,需要的小伙伴可以了解下
    2024-02-02
  • Golang channel死鎖的幾種情況小結(jié)

    Golang channel死鎖的幾種情況小結(jié)

    本文主要介紹了Golang channel死鎖的幾種情況小結(jié),詳細的介紹了六種情況,具有一定的參考價值,感興趣的可以了解一下
    2024-08-08
  • 詳解Go語言中獲取文件路徑的不同方法與應(yīng)用場景

    詳解Go語言中獲取文件路徑的不同方法與應(yīng)用場景

    在使用?Go?開發(fā)項目時,估計有不少人遇到過無法正確處理文件路徑的問題,本文將嘗試從簡單到復(fù)雜,詳細介紹?Go?中獲取路徑的不同方法及應(yīng)用場景,希望對大家有所幫助
    2024-02-02
  • golang 實現(xiàn)一個負載均衡案例(隨機,輪訓(xùn))

    golang 實現(xiàn)一個負載均衡案例(隨機,輪訓(xùn))

    這篇文章主要介紹了golang 實現(xiàn)一個負載均衡案例(隨機、輪訓(xùn)),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Go 通過結(jié)構(gòu)struct實現(xiàn)接口interface的問題

    Go 通過結(jié)構(gòu)struct實現(xiàn)接口interface的問題

    這篇文章主要介紹了Go 通過結(jié)構(gòu)struct實現(xiàn)接口interface的問題,本文通過示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-10-10

最新評論