自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

這兒幾個字節(jié),那里幾個字節(jié),我們說的是真正的內(nèi)存

開發(fā)
這是謎題的最后一塊拼圖。Go 1.15 版本沒有 24 字節(jié)的尺寸類別,因此 ss 的堆分配是在 32 字節(jié)的尺寸類別中分配的。

今天的帖子來自于最近的 Go 語言的一次小測試,觀察下面的測試基礎(chǔ)片段 [1]

func BenchmarkSortStrings(b *testing.B) {
        s := []string{"heart", "lungs", "brain", "kidneys", "pancreas"}
        b.ReportAllocs()
        for i := 0; i < b.N; i++ {
                sort.Strings(s)
        }
}

sort.Strings 是 sort.StringSlice(s) 的便捷包裝器,sort.Strings 在原地對輸入進(jìn)行排序,因此不會分配內(nèi)存(或至少 43% 回答此問題的 Twitter 用戶是這么認(rèn)為的)。然而,至少在 Go 的最近版本中,基準(zhǔn)測試的每次迭代都會導(dǎo)致一次堆分配。為什么會是這種情況?

正如所有 Go 程序員應(yīng)該知道的那樣,接口是以 雙詞結(jié)構(gòu) 實(shí)現(xiàn)的。每個接口值包含一個字段,其中保存接口內(nèi)容的類型,以及指向接口內(nèi)容的指針。[2]

在 Go 語言偽代碼中,一個接口可能是這樣的:

type interface struct {
        // the ordinal number for the type of the value
        // assigned to the interface 
        type uintptr
        // (usually) a pointer to the value assigned to
        // the interface
        data uintptr
}

interface.data 可以容納一個機(jī)器字(在大多數(shù)情況下為 8 個字節(jié)),但一個 []string 卻需要 24 個字節(jié):一個字用于指向切片的底層數(shù)組;一個字用于存儲切片的長度;另一個字用于存儲底層數(shù)組的剩余容量。那么,Go 是如何將 24 個字節(jié)裝入個 8 個字節(jié)的呢?通過編程中最古老的技巧,即間接引用。一個 []string,即 s,需要 24 個字節(jié);但 *[]string —— 即指向字符串切片的指針,只需要 8 個字節(jié)。

逃逸到堆

為了讓示例更加明確,以下是重新編寫的基準(zhǔn)測試,不使用 sort.Strings 輔助函數(shù):

func BenchmarkSortStrings(b *testing.B) {
        s := []string{"heart", "lungs", "brain", "kidneys", "pancreas"}
        b.ReportAllocs()
        for i := 0; i < b.N; i++ {
                var ss sort.StringSlice = s
                var si sort.Interface = ss // allocation
                sort.Sort(si)
        }
}

為了讓接口正常運(yùn)行,編譯器將賦值重寫為 var si sort.Interface = &ss,即 ss 的地址分配給接口值。[3] 我們現(xiàn)在有這么一種情況:出現(xiàn)一個持有指向 ss 的指針的接口值。它指向哪里?還有 ss 存儲在哪個內(nèi)存位置?

似乎 ss 被移動到了堆上,這也同時(shí)導(dǎo)致了基準(zhǔn)測試報(bào)告中的分配:

Total:    296.01MB   296.01MB (flat, cum) 99.66%
      8            .          .           func BenchmarkSortStrings(b *testing.B) { 
      9            .          .           	s := []string{"heart", "lungs", "brain", "kidneys", "pancreas"} 
     10            .          .           	b.ReportAllocs() 
     11            .          .           	for i := 0; i < b.N; i++ { 
     12            .          .           		var ss sort.StringSlice = s 
     13     296.01MB   296.01MB           		var si sort.Interface = ss // allocation 
     14            .          .           		sort.Sort(si) 
     15            .          .           	} 
     16            .          .           }

發(fā)生這種分配是因?yàn)榫幾g器當(dāng)前無法確認(rèn) ss 比 si 生存期更長。Go 編譯器開發(fā)人員對此的普遍態(tài)度是,覺得 這個問題改進(jìn)的余地,不過我們另找時(shí)間再議。事實(shí)上,ss 就是被分配到了堆上。因此,問題變成了:每次迭代會分配多少個字節(jié)?為什么不去詢問 testing 包呢?

% go test -bench=. sort_test.go
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-5650U CPU @ 2.20GHz
BenchmarkSortStrings-4          12591951                91.36 ns/op           24 B/op          1 allocs/op
PASS
ok      command-line-arguments  1.260s

可以看到,在 amd 64 平臺的 Go 1.16 beta1 版本上,每次操作會分配 24 字節(jié)。[4] 然而,在同一平臺先前的 Go 版本中,每次操作則消耗了 32 字節(jié)。

% go1.15 test -bench=. sort_test.go
goos: darwin
goarch: amd64
BenchmarkSortStrings-4          11453016                96.4 ns/op            32 B/op          1 allocs/op
PASS
ok      command-line-arguments  1.225s

這引出了本文的主題,即 Go 1.16 版本中即將推出的一項(xiàng)便利改進(jìn)。不過在討論這個內(nèi)容之前,我需要聊聊 “尺寸類別size class”。

尺寸類別

在解釋什么是 “尺寸類別size class” 之前,我們先考慮個問題,理論上的 Go 語言在運(yùn)行時(shí)是如何在其堆上分配 24 字節(jié)的。有一個簡單的方法:追蹤目前為止已分配到的所有內(nèi)存的動向——利用指向堆上最后分配的字節(jié)的指針。分配 24 字節(jié),堆指針就會增加 24,然后將前一個值返回給調(diào)用函數(shù)。只要寫入的請求 24 字節(jié)的代碼不超出該標(biāo)記的范圍,這種機(jī)制就沒有額外開銷。不過,現(xiàn)實(shí)情況下,內(nèi)存分配器不僅要分配內(nèi)存,有時(shí)還得釋放內(nèi)存。

最終,Go 語言程序在運(yùn)行時(shí)將釋放這些 24 字節(jié),但從運(yùn)行的視角來看,它只知道它給調(diào)用者的開始地址。它不知道從該地址起始之后又分配了多少字節(jié)。為了允許釋放內(nèi)存,我們假設(shè)的 Go 語言程序運(yùn)行時(shí)分配器必須記錄堆上每個分配的長度值。那么這些長度值的分配存儲在何處?當(dāng)然是在堆上。

在我們的設(shè)想中,當(dāng)程序運(yùn)行需要分配內(nèi)存的時(shí)候,它可以請求稍微多一點(diǎn),并把它用來存儲請求的數(shù)量。而對于我們的切片示例而言,當(dāng)我們請求 24 字節(jié)時(shí),實(shí)際上會消耗 24 字節(jié)加上存儲數(shù)字 24 的一些開銷。這些開銷有多大?事實(shí)上,實(shí)際上的最小開銷量是一個字。[5]

用來記錄 24 字節(jié)分配的開銷將是 8 字節(jié)。25% 不是很大,但也不算糟糕,隨著分配的大小增加,開銷將變得微不足道。然而,如果我們只想在堆上存儲一個字節(jié),會發(fā)生什么?開銷將是請求數(shù)據(jù)量的 8 倍!是否有一種更高效的方式在堆上分配少量內(nèi)存?

與其在每個分配旁邊存儲長度,不如將相同大小的內(nèi)容存儲在一起,這個主意如何?如果所有的 24 字節(jié)的內(nèi)容都存儲在一起,那么運(yùn)行時(shí)會自動獲取它們的大小。運(yùn)行時(shí)所需要的是一個單一的位,指示 24 字節(jié)區(qū)域是否在使用中。在 Go 語言中,這些區(qū)域被稱為 Size Classes,因?yàn)橄嗤笮〉乃袃?nèi)容都會存儲在一起(類似學(xué)校班級,所有學(xué)生都按同一年級分班,而不是 C++ 中的類)。當(dāng)運(yùn)行時(shí)需要分配少量內(nèi)存時(shí),它會使用能夠容納該分配的最小的尺寸類別。

無限制的尺寸類別

現(xiàn)在我們知道尺寸類別是如何工作的了,那么問題又來了,它們存儲在哪里?和我們想的一樣,尺寸類別的內(nèi)存來自堆。為了最小化開銷,運(yùn)行時(shí)會從堆上分配較大的內(nèi)存塊(通常是系統(tǒng)頁面大小的倍數(shù)),然后將該空間用于單個大小的分配。不過,這里存在一個問題————

將大塊區(qū)域用于存儲同一大小的事物的模式很好用 [6],如果分配大小的數(shù)量是固定的,最好是少數(shù)幾個。那么在通用語言中,程序可以要求運(yùn)行時(shí)以任何大小分配內(nèi)存[7]。

例如,想象一下向運(yùn)行時(shí)請求 9 字節(jié)。9 字節(jié)是一個不常見的大小,因此可能需要一個新的尺寸類別來存儲 9 字節(jié)大小的物品。因?yàn)?9 字節(jié)大小的物品不常見,所以分配的其余部分(通常為 4KB 或更多)可能會被浪費(fèi)。由于尺寸類別的集合是固定的,如果沒有精確匹配的 size class 可用,分配將并入到下一個尺寸類別。在我們的示例中,9 字節(jié)可能會在 12 字節(jié)的尺寸類別中分配。未使用的 3 字節(jié)的開銷要比幾乎未使用的整個尺寸類別分配好。

總結(jié)一下

這是謎題的最后一塊拼圖。Go 1.15 版本沒有 24 字節(jié)的尺寸類別,因此 ss 的堆分配是在 32 字節(jié)的尺寸類別中分配的。由于 Martin M?hrmann 的工作,Go 1.16 版本有一個 24 字節(jié)的尺寸類別,非常適合分配給接口的切片值。

相關(guān)文章

  1. 我在 Devfest 2017年西伯利亞大會談 Go 語言
  2. 如果對齊的內(nèi)存寫操作是原子性的,為什么我們還需要 sync/atomic 包呢?
  3. 為你的樹莓派創(chuàng)建一個真實(shí)的串行控制臺
  4. 為什么 Go 語言線程的棧是無限制的?
責(zé)任編輯:龐桂玉 來源: Linux中國
相關(guān)推薦

2018-05-29 16:43:51

VDIIDV字母

2024-04-07 01:00:00

模型P圖

2010-05-11 19:01:11

Unix系統(tǒng)

2024-01-10 17:45:41

模型數(shù)據(jù)

2022-02-11 12:55:00

前綴索引索引值

2021-01-26 01:55:24

HTTPS網(wǎng)絡(luò)協(xié)議加密

2021-03-15 11:20:46

HTTPS優(yōu)化前端

2021-09-06 11:34:47

架構(gòu)微服務(wù)Hystrix

2020-06-11 13:31:45

TCP序列號網(wǎng)絡(luò)

2015-09-21 08:45:00

2011-12-18 21:39:21

iOS 5

2021-02-15 12:11:00

開發(fā)技巧

2013-03-22 15:40:32

VS項(xiàng)目整體命名.NET

2021-11-04 11:54:30

Linux內(nèi)存系統(tǒng)

2022-01-16 20:26:50

機(jī)器人操作系統(tǒng)ROS

2010-09-25 15:52:27

JVM內(nèi)存JVM

2019-08-28 18:24:13

SaaS云計(jì)算企業(yè)

2013-08-22 09:54:14

創(chuàng)業(yè)管理

2021-10-20 20:24:53

辦公

2021-09-08 08:34:37

Go 文檔Goland
點(diǎn)贊
收藏

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