成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

這兒幾個字節,那里幾個字節,我們說的是真正的內存

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

今天的帖子來自于最近的 Go 語言的一次小測試,觀察下面的測試基礎片段 [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 在原地對輸入進行排序,因此不會分配內存(或至少 43% 回答此問題的 Twitter 用戶是這么認為的)。然而,至少在 Go 的最近版本中,基準測試的每次迭代都會導致一次堆分配。為什么會是這種情況?

正如所有 Go 程序員應該知道的那樣,接口是以 雙詞結構 實現的。每個接口值包含一個字段,其中保存接口內容的類型,以及指向接口內容的指針。[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 可以容納一個機器字(在大多數情況下為 8 個字節),但一個 []string 卻需要 24 個字節:一個字用于指向切片的底層數組;一個字用于存儲切片的長度;另一個字用于存儲底層數組的剩余容量。那么,Go 是如何將 24 個字節裝入個 8 個字節的呢?通過編程中最古老的技巧,即間接引用。一個 []string,即 s,需要 24 個字節;但 *[]string —— 即指向字符串切片的指針,只需要 8 個字節。

逃逸到堆

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

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)
        }
}

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

似乎 ss 被移動到了堆上,這也同時導致了基準測試報告中的分配:

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            .          .           }

發生這種分配是因為編譯器當前無法確認 ss 比 si 生存期更長。Go 編譯器開發人員對此的普遍態度是,覺得 這個問題改進的余地,不過我們另找時間再議。事實上,ss 就是被分配到了堆上。因此,問題變成了:每次迭代會分配多少個字節?為什么不去詢問 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 字節。[4] 然而,在同一平臺先前的 Go 版本中,每次操作則消耗了 32 字節。

% 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 版本中即將推出的一項便利改進。不過在討論這個內容之前,我需要聊聊 “尺寸類別size class”。

尺寸類別

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

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

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

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

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

無限制的尺寸類別

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

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

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

總結一下

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

相關文章

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

2018-05-29 16:43:51

VDIIDV字母

2024-04-07 01:00:00

模型P圖

2010-05-11 19:01:11

Unix系統

2024-01-10 17:45:41

模型數據

2022-02-11 12:55:00

前綴索引索引值

2021-01-26 01:55:24

HTTPS網絡協議加密

2021-03-15 11:20:46

HTTPS優化前端

2020-06-11 13:31:45

TCP序列號網絡

2021-09-06 11:34:47

架構微服務Hystrix

2011-12-18 21:39:21

iOS 5

2015-09-21 08:45:00

2013-03-22 15:40:32

VS項目整體命名.NET

2021-02-15 12:11:00

開發技巧

2021-11-04 11:54:30

Linux內存系統

2022-01-16 20:26:50

機器人操作系統ROS

2019-08-28 18:24:13

SaaS云計算企業

2013-08-22 09:54:14

創業管理

2010-09-25 15:52:27

JVM內存JVM

2021-10-20 20:24:53

辦公

2021-09-08 08:34:37

Go 文檔Goland
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久av中文字幕 | 亚洲精品久久久蜜桃网站 | 蜜臀网| 一级日韩 | 久久人操 | 日韩高清中文字幕 | 亚洲欧美视频一区 | 给我免费的视频在线观看 | 久久久无码精品亚洲日韩按摩 | 国产日韩精品视频 | 免费性视频 | 97精品国产一区二区三区 | 91在线精品秘密一区二区 | 久久成人亚洲 | 日韩精品一区二区三区视频播放 | 91色视频在线观看 | 综合欧美亚洲 | 亚洲国产精品视频 | 91久久国产 | 国产精品久久久久久久久久久久久久 | 国产精品a久久久久 | 国产ts人妖另类 | 亚洲瑟瑟 | 国产精品视频在线观看 | 久久aⅴ乱码一区二区三区 亚洲国产成人精品久久久国产成人一区 | 二区三区av | 日韩中文字幕一区 | 在线a视频网站 | 欧美一级大片免费看 | 久久成人免费视频 | 国产一卡二卡三卡 | 91亚洲精品在线 | 狼人伊人影院 | 久久噜噜噜精品国产亚洲综合 | 剑来高清在线观看 | 久草青青草 | 国产精品久久久久aaaa九色 | 一区二区三区在线免费观看视频 | 综合五月 | 久久久精品 | 免费观看一级毛片 |