聽 GPT 講 Go 源代碼 - malloc-go

File: malloc.go

malloc.go 文件是 Go 語言中管理內存分配和釋放的核心文件之一。它包含了 Go 語言的內存管理器(Memory Allocator)實現。

Go 語言使用了一種名爲 mcache 的內存管理技術,它是一種基於線程本地(Thread-Local)緩存的內存分配器,它將一些特定大小的內存分配請求分配到緩存線程中來處理,以減少對全局內存池的訪問。malloc.go 文件中定義了 mcache 相關的結構體、變量和函數。同時,對於比較大的內存分配請求,Go 語言採用了基於堆內存的分配方式。

該文件對於 Go 語言的運行時來說是非常重要的,因爲它直接影響了程序執行的性能和穩定性。由於 Go 語言的內存管理有着極高的效率和靈活性,使得它在高性能併發編程中廣受歡迎。


Var:

physPageSize

在 Go 語言的運行時系統中,malloc.go 這個文件定義了內存分配函數。其中,physPageSize 這個變量是用來記錄當前系統的物理內存頁大小的。具體作用如下:

  1. 內存對齊:內存的分配和釋放需要滿足一定的內存對齊要求,這個對齊的單位就是物理內存頁大小。例如,如果物理內存頁大小是 4KB,那麼分配 8 字節的內存時,實際上會分配 2 個物理內存頁的大小,也就是 8KB 的內存。

  2. 內存池管理:Go 語言的運行時系統使用了內存池來管理內存的分配和釋放。內存池的實現需要考慮物理內存頁大小。如果分配的內存大小小於物理內存頁大小,那麼會從內存池中找到一個已經分配過的內存塊來使用,而不是直接向操作系統請求分配內存。這樣可以提高內存的使用效率。

  3. 內存映射:Go 語言的運行時系統也使用了內存映射來管理內存。內存映射可以將物理內存頁映射到虛擬內存地址上,從而實現對物理內存的管理。在內存映射中,需要考慮物理內存頁的大小,因爲內存映射是以物理內存頁爲單位進行的。如果物理內存頁大小不正確,可能會導致內存映射出錯。

總之,physPageSize 變量是 Go 語言運行時系統中用來記錄當前系統的物理內存頁大小的變量,它對於內存分配、對齊、池管理和映射等方面都具有重要作用。

physHugePageSize

在 Go 語言中,malloc.go 文件主要用於管理內存分配和回收。其中,physHugePageSize 這個變量用於表示操作系統支持的最大內存頁面大小(單位爲字節)。如果操作系統不支持大頁面,則該變量默認爲 0。

在程序啓動時,Go 運行時會根據操作系統的支持情況來設置 physHugePageSize 變量的值。如果操作系統支持大頁面,則該變量的值會被設置爲相應的頁面大小,從而可以更高效地進行內存分配和管理。否則,程序將使用操作系統默認的頁面大小來進行內存管理。

physHugePageSize 變量的值還會影響到內存分配器的一些具體實現細節,例如在進行大塊內存分配時,內存分配器會優先選擇使用大頁面來分配內存,從而減少內存碎片和內存管理的開銷。此外,如果系統支持大頁面,則內存分配器還會嘗試合併相鄰的內存頁面,從而提高內存分配的效率。

總之,physHugePageSize 變量是 Go 運行時中一個重要的配置參數,它影響到內存分配和管理的效率和質量,需要根據具體操作系統和應用場景來進行設置和調整。

physHugePageShift

在 Go 語言的運行時中,malloc.go 文件負責 Go 程序的內存分配和管理。physHugePageShift 是該文件中的一個變量,其作用是用於確定系統支持的大頁的大小。

大頁是計算機系統中用於提高內存訪問性能的機制,它們的大小通常比標準頁大很多。在支持大頁的系統中,如果應用程序使用大頁來管理內存,則可以從以下方面獲得明顯的性能提升:

  1. 減少內存訪問的 TLB 缺失次數。因爲大頁覆蓋的內存範圍比標準頁大,所以在大頁管理的內存範圍內,每個 TLB(Translation Lookaside Buffer,翻譯高速緩存)緩存的虛擬地址對應的物理地址也就更多。在訪問時,CPU 可以使用更少的 TLB 緩存去覆蓋更多的內存地址,從而顯著減少 TLB 缺失次數,提高內存訪問效率。

  2. 減少頁表項的數量。因爲大頁覆蓋的內存範圍比標準頁大很多,所以使用大頁管理內存時,需要的頁表項數量也就比標準頁少很多。這樣就可以減少頁表大小,從而提高內存管理效率。

在 Go 語言中,當程序需要向操作系統申請大頁時,就需要使用 physHugePageShift 變量來確定操作系統支持的大頁的大小。在 Windows 環境下,變量的默認值是 0,表示不使用大頁。而在 Linux 環境下,變量的默認值是 30,表示使用 2MB 的大頁,但也可以根據系統情況進行調整。通過調整變量的值,程序可以靈活地使用大頁機制來提高內存訪問效率和管理效率。

zerobase

在 Go 語言中,malloc.go文件是一個實現內存分配器的重要文件,其中zerobase是一個指向零值的指針。它的作用是在調用sysAlloc()函數時,將申請的內存的初始值初始化爲零值。與 C/C++ 中的calloc()函數類似,它可以保證動態分配的內存塊的初始值爲零,從而避免了未初始化內存帶來的安全隱患。

zerobase的定義如下:

var zerobase *byte

// ...

// Allocate size bytes of zeroed memory. 
func calloc(size uintptr) unsafe.Pointer {
    // ...

    p := sysAlloc(size, &memstats.other_sys)
    if p != nil {
        *(*uintptr)(unsafe.Pointer(p)) = size
        p += ptrSize
        if zerobase != nil {
            // If we're using a garbage collector, we need to make
            // sure that any memory allocated by the application is
            // initialized with zeros. We only do this if the memory
            // returned by the allocator is contiguous with the
            // zerobase region, forcing all Go allocations, no matter
            // the source (cgo, Go), to be forced to go through the
            // garbage collector. This is important for Go 1.5, where
            // the garbage collector checks if an object's pointer is
            // in a heap chunk that is still allocated.
            var sz uintptr
            n := numPhysPages
            for i := 0; i < n; i++ {
                if uintptr(unsafe.Pointer(&zerobase[i*physPageSize]))+size > uintptr(p) {
                    break
                }
                sz = size
            }
            if sz > 0 {
                memclrNoHeapPointers(p, size)
            }
        }
    }

    // ...
}

當我們調用calloc()函數時,它會首先調用sysAlloc()函數來申請指定大小的內存塊。然後,calloc()函數判斷zerobase是否爲空,如果不爲空,說明申請的內存塊與zerobase指向的內存是相鄰的,那麼它就會將申請的內存塊初始化爲零值,確保其始終是正確的。因此,zerobase的作用是爲了保證內存分配器能夠使用非常快的方式去清空申請到的內存塊,避免內存帶來的問題。

globalAlloc

globalAlloc 是一個指向 runtime.mheap 類型的全局變量。它用於管理和分配堆空間。具體來說,它的作用包括以下幾個方面:

  1. 管理堆空間:globalAlloc 是 runtime.mheap 的入口點,它包含了所有堆管理器的信息和狀態。通過 globalAlloc,可以對堆空間進行分配、釋放等操作。

  2. 內存分配:當 Go 程序需要分配內存時,會調用 globalAlloc 中的 alloc 函數。alloc 函數會在堆空間中尋找一段合適的空閒內存塊,並返回其地址,供程序使用。

  3. 垃圾回收:Go 語言使用了自動垃圾回收機制。globalAlloc 會在引用計數器被清零時,調用堆空間的垃圾回收器。垃圾回收器會遍歷堆空間中的所有對象,清理不再使用的垃圾內存,爲程序釋放內存空間。

  4. 管理內存對象:globalAlloc 還會管理堆空間中的所有內存對象。它會維護內存對象的元數據(如內存塊大小、使用狀態等),並進行相應的內存分配和回收操作。

總之,globalAlloc 是一個非常重要的變量,它直接影響了 Go 程序的內存使用效率和性能。理解和掌握 globalAlloc 的作用,對於深入瞭解 Go 語言的內存管理機制和 GC 算法是非常有幫助的。

persistentChunks

在 go 語言中,所有的內存分配都是由 runtime 庫中的 malloc 函數來完成的。malloc.go 文件是 runtime 庫的一個重要組成部分,它定義了一些與內存分配相關的數據結構和函數。其中,persistentChunks 變量是一個指向 Chunk 結構體的指針數組,用於存儲在程序生命週期中一直存在的 Chunk 結構體。

Chunk 結構體代表了一段內存塊,幷包含以下屬性:

當程序需要分配內存時,malloc 函數會首先在 persistentChunks 中查找是否有足夠的 Chunk 結構體來滿足當前請求。如果沒有足夠的 Chunk 結構體,malloc 函數會調用 chunkAlloc 函數來分配額外的 Chunk 結構體,並將它們添加到 persistentChunks 中。

通過使用 persistentChunks 變量,runtime 庫可以有效地維護和管理內存塊,減少了頻繁地申請和釋放內存的開銷,提高了程序的性能和穩定性。


Structs:

persistentAlloc

persistentAlloc 是用來管理內存永久化分配的結構體。在 Go 程序中,內存分配後會反覆使用,但是當程序結束後,這塊內存就會被釋放回操作系統。爲了保留程序退出後分配的內存,可以使用 persistentAlloc 來進行內存永久化的管理。

persistentAlloc 使用 mmap 系統調用來分配內存,並將內存映射到文件中。在程序退出時,persistentAlloc 會將內存持久化到磁盤中。當下次程序啓動時,persistentAlloc 會重新映射之前持久化的內存。

這種方式實現了內存永久化,可以用於需要長時間運行的服務。可以將一些常用的數據結構,比如哈希表、緩存等,持久化到磁盤中,避免程序重啓時重新計算。

值得注意的是,persistentAlloc 只適用於 32 位系統,因爲在 64 位系統中,mmap 的地址空間太大,持久化的開銷太大。同時,在使用 persistentAlloc 時,也需要注意內存映射文件的大小不能超過系統限制。

linearAlloc

在 Go 語言的運行時系統中,linearAlloc 結構體是用於管理分配內存的結構體。它的作用是爲即將分配的小型對象預分配內存,以提高分配內存的效率。

具體來說,linearAlloc 結構體是由一個或多個內存塊組成的,每個內存塊都是一段連續的內存區域。在分配內存時,Go 語言運行時系統首先檢查上一個分配的內存塊是否有剩餘空間,如果有,則直接從其中分配空間。如果沒有,則從當前的內存塊中申請一個新的內存塊,並將其添加到線性分配器的內存塊列表中。

使用 linearAlloc 結構體的好處是可以避免頻繁調用系統 malloc 函數,從而減少分配內存的開銷。此外,由於分配的內存在同一個內存塊中,因此內存訪問的連續性得到了保證,可以提高程序的訪問效率。

總之,linearAlloc 結構體是 Go 語言運行時系統內存管理的重要組成部分,能夠提高程序的性能和內存使用效率。

notInHeap

notInHeap 是一個包含指針的結構體,它的作用是標識一個對象不應該被分配到堆(heap)中。在 Go 語言中,GC(垃圾回收)是自動管理內存的,它會自動跟蹤哪些對象是可達的,哪些對象是需要被回收的,以及哪些對象是不需要被回收的。不過,有些對象即使不用回收也不應該放在堆上,因爲它們可能會被其它機制(例如 Cgo)釋放掉,這樣就可能破壞了 GC 的一些假設,導致程序出現問題。

在 malloc.go 文件中,notInHeap 結構體被用於一些特殊情況下的內存分配。例如,當使用 Cgo 調用外部函數時,它可能會返回一個指向不在堆中的內存區域的指針;在這種情況下,如果將這個指針傳遞給 GC,GC 可能會誤認爲這是一段內存地址並嘗試對其進行回收,導致程序崩潰。

因此,notInHeap 結構體的作用就是標識這些不應該被 GC 回收的內存區域,從而避免這樣的問題發生。notInHeap 的定義很簡單,只是一個指針類型的成員變量,不過在 Go 語言的 GC 機制中卻非常有用。

Functions:

mallocinit

在 Go 語言中,malloc.go 文件中的 mallocinit 函數主要用於初始化堆和 gc 相關的函數和參數。具體來說,該函數會執行以下幾個操作:

  1. 調用 mheap 初始化堆。mheap 是 Go 語言中用於管理堆的主要數據結構,包括對內存分配和回收的實現。

  2. 初始化 gc 全局變量。包括 gcpercent、gcphase 和 gcBgMarkWorkerMode 等參數,用於控制垃圾回收的速度和方式。

  3. 創建一個 goroutine 用於後臺 gc。該 goroutine 會定期檢查 gc 相關的參數和狀態,以確定何時執行垃圾回收操作。

  4. 初始化 malloc 相關的數據結構和參數。這些參數包括 minblocksize、maxblocksize 和 pagemask 等,用於控制內存分配的大小和方式。

總之,mallocinit 函數是 Go 語言中非常重要的一個函數,它負責初始化堆和內存分配等相關的參數和函數,保證程序能正常使用和管理內存。

sysAlloc

sysAlloc 函數是 Go 語言中的內存分配器的底層實現函數。它的作用是從操作系統申請一段內存空間,並返回相應的指針,供其上層函數使用。在 Go 語言的內存分配器中,sysAlloc 函數主要負責內存的分配,以及對分配後的內存空間進行初始化等工作。

sysAlloc 函數的具體實現依賴於操作系統的不同,但是通常它會調用一些系統級別的函數,如 mmap 或 VirtualAlloc 等,來實現內存分配。這些函數提供了高效的內存分配方式,並且可以提高應用程序的性能。

除了 sysAlloc 函數,Go 語言中的內存分配器還包括其他一些函數,如 mallocgc 和 calloc 等。這些函數在實現上都是依賴於 sysAlloc 函數的。它們的主要作用就是根據不同的需求,調用 sysAlloc 函數進行內存的分配。

總之,sysAlloc 函數是 Go 語言中內存分配器的重要組成部分,它的主要作用是實現內存分配和對分配後的內存空間進行初始化等工作,從而爲上層函數提供高效的內存管理方式。

sysReserveAligned

sysReserveAligned 是一個在 malloc.go 中的函數,其作用是在進程的虛擬地址空間中分配一段連續的、對齊的、未映射的內存區域,並返回該區域的首地址。sysReserveAligned 一般會分配更大的區域,用於備用和未來的需求。下面對該函數的詳細介紹如下:

函數原型:

func sysReserveAligned(bytes uintptr, align uintptr) unsafe.Pointer

參數:

返回值:

該函數內部通過調用 runtime_entersyscall 和 runtime_exitsyscall 兩個函數實現進程在系統調用期間的鎖定和解鎖。在分配內存前,該函數首先會將參數 bytes 和 align 進行向上舍入,以保證內存區域滿足對齊要求。然後,該函數會調用 sysReserve 函數分配更大的連續內存區域,其中 align 參數用於保證分配的地址對齊。最後,該函數返回未映射區域的首地址。

總體來說,sysReserveAligned 函數用於在進程的虛擬地址空間中分配對齊的、未映射的內存區域,這些區域一般用於備用和未來的需求。而 sysReserveAligned 函數是在 malloc.go 文件中實現的,在 Go 語言的運行時系統中扮演着重要的角色,確保了內存分配的高效性和可靠性。

enableMetadataHugePages

enableMetadataHugePages 函數的主要作用是啓用或禁用元數據頁的大頁面分配。

在操作系統內核中,物理頁框是由系統分配的連續物理內存塊,每個物理頁框的大小通常是 4KB。對於大內存的應用程序,如果使用大量的小頁面 (4KB) 來進行內存分配,會導致大量的內存碎片,浪費系統內存資源,並且降低系統的性能。針對這種情況,可以考慮使用大頁面 (如 2MB 或 1GB) 來進行內存分配,可以減少內存碎片,提高內存利用率和應用程序性能。

而對於 Go 語言的堆內存中的元數據頁,也可以採用大頁面來分配,需要使用一些特定的系統調用才能實現。其中,enableMetadataHugePages 函數就是實現這種大頁面分配的關鍵。

enableMetadataHugePages 函數的實現過程如下:

  1. 首先,通過 syscall.NumHugePages 函數獲取系統支持的大頁面的數量和大小 (單位是 page size)。

  2. 然後,計算出元數據頁需要的大頁面數,和每個大頁面的大小。

  3. 如果元數據頁需要的大頁面數小於系統支持的大頁面數,則通過 syscall.Mmap 函數映射相應數量的大頁面,實現元數據頁的大頁面分配。

  4. 如果元數據頁需要的大頁面數大於系統支持的大頁面數,則調用 runtime·printf 函數打印一條錯誤信息,並返回失敗。

  5. 如果元數據頁的大頁面分配成功,則通過調用 sysUnused 函數標記大頁面爲已使用狀態,避免被其他程序或進程使用。

總之,enableMetadataHugePages 函數的主要作用是實現元數據頁的大頁面分配,以提高系統內存利用率和 Go 程序性能。

nextFreeFast

nextFreeFast 函數是 Go 語言運行時中的內存分配器 malloc 的一部分,它的作用是從位圖中查找下一個空閒的頁框,並返回其地址。

在 Go 語言中,內存分配器 malloc 的主要職責是爲程序動態分配內存。爲了優化內存分配的性能,malloc 將大塊的內存分割成小塊,然後將每個小塊與一個位圖中的位(bit)對應。這個位圖記錄了當前哪些塊是被佔用的,哪些塊是空閒的。當應用程序需要動態分配內存時,malloc 會在位圖中查找一個空閒的塊,將其標記爲已佔用,並返回其地址。

nextFreeFast 函數是 malloc 的一部分,其主要任務是實現快速的位圖掃描,以查找下一個未被佔用的塊。在實現上,它將位圖按照 8 個字節(64 位)進行分塊,快速地掃描每個塊,以查找該塊中第一個爲 0 的位。最終,該函數會返回第一個未被佔用的塊的地址。

重要的是要注意,nextFreeFast 只是內存分配器 malloc 的一部分。在 Go 語言中,內存在創建時並不會立刻被 malloc 分配。取而代之的是,malloc 會在需要時爲應用程序動態地分配內存。因此,nextFreeFast 函數會被調用多次,並且在不同的時間點分別執行。每次調用都會搜索位圖,查找下一個未被佔用的塊。

nextFree

nextFree 函數是 Go 語言中的堆內存分配器的一個函數,其作用是在當前的 heapArena 對象中尋找下一個可用的 chunk 區域。

在 Go 語言中,內存的分配是通過堆進行的。當程序需要分配內存時,Go 語言會從 heapArena 中選擇一個空閒的 chunk 區域來分配。nextFree 函數會在 heapArena 中從當前的 chunk 區域開始尋找,如果當前的 chunk 區域已經被分配,就會繼續尋找下一個可用的 chunk 區域,直到找到一個未分配的 chunk 區域爲止。

該函數首先會檢查當前的 chunk 區域是否已經被分配,如果已經被分配,則會跳轉到下一個 chunk 區域。如果遍歷完所有的 chunk 區域還未找到可用區域,則會重新分配一個新的 chunk 空間。

通過 nextFree 函數的調用,Go 語言可以在堆內存中高效地分配內存空間,確保程序的運行效率和資源利用率。

mallocgc

在 go 語言中,使用 malloc 函數來分配內存。malloc 函數是在操作系統中分配內存的,它會在操作系統中申請一塊內存空間,並返回一個指向這塊內存的指針。但是,go 語言提供了自己的內存管理機制,其中包括適用於固定大小的對象的堆空間,適用於大對象的分配器等。mallocgc 是其中的一個函數,它的作用是用於從堆空間分配內存。

具體來說,mallocgc 函數分配了一段大小爲 size 的內存空間,並返回一個指向這段內存空間的指針。在這個過程中,如果堆空間不足以容納大小爲 size 的內存空間,那麼 mallocgc 函數會使用垃圾回收器來釋放不再使用的內存空間,以便爲新的內存分配騰出空間。在分配內存的過程中,mallocgc 函數會考慮對象大小和對齊等因素,以保證分配的內存空間可以有效地被利用和管理。

因此,mallocgc 函數是 go 語言中內存管理機制的核心之一。它負責從堆空間分配內存,並確保這些內存可以被有效地管理和利用,同時也保證了垃圾回收功能的正常運行。

deductAssistCredit

deductAssistCredit 是 Go 語言中管理內存的函數之一,主要用於協助內存分配器(memory allocator)選擇 Goroutine(Go 語言中的協程)隊列中最適合的 Goroutine 來執行分配或釋放內存的操作。

當我們在 Go 語言中使用 new 或 make 操作符來動態分配內存時,Go 語言的運行時系統(runtime)會自動爲我們管理內存分配和回收的操作。在分配內存時,Go 語言的運行時系統會判斷當前需要運行的 Goroutine 的活躍程度,爲其分配相應的內存,從而儘可能減少內存的浪費。

其中,deductAssistCredit 函數的作用是爲分配器(memory allocator)提供 “扣除協程信用”(deduct assist credit)的功能。所謂協程信用是指分配器爲每個協程分配的一定數量的內存。當某個協程需要分配一定數量的內存時,分配器會檢查其協程信用是否足夠,如果足夠,則直接分配,如果不足,則需要扣除協程信用以避免內存浪費。

具體而言,deductAssistCredit 函數的主要實現是通過一定的算法計算當前協程的休眠時間,然後根據該時間向該協程的協程信用中扣除一定數量的信用分數,從而降低該協程在內存分配過程中的優先級,使得其他協程有更大的機會來分配或釋放內存操作。

總之,deductAssistCredit 函數的作用是協助內存分配器選擇最適合的 Goroutine 來執行分配或釋放內存的操作,以實現更加高效和智能的內存管理。

memclrNoHeapPointersChunked

memclrNoHeapPointersChunked 是 runtime 包中 malloc.go 文件中的函數之一,其主要作用是清空給定的內存塊。

在實際的編程中,經常需要清空內存塊,以便對其進行重新利用或重置。在 Go 語言中,可以使用內置函數makenew創建動態分配內存塊。但是,使用動態分配的內存塊時,需要清空其中的數據。而 memclrNoHeapPointersChunked 函數就是用來實現清空內存塊的操作。

memclrNoHeapPointersChunked 函數採用分塊的方式進行內存清零操作,每塊內存大小爲 chunkSize(默認值爲 256 字節)。在清空內存的過程中,函數會跳過指向堆的指針,不會影響已經分配的堆內存。

通過 memclrNoHeapPointersChunked 函數清空內存塊可以有效地提高程序的運行效率,因爲該函數的底層實現是用匯編代碼編寫的,運行速度非常快,特別是在大規模內存清空操作時,效率尤爲顯著。

newobject

newobject 函數的作用是用於分配一個新的對象,並返回一個指向該對象的指針。

具體來說,newobject 函數首先根據給定的類型 size 確定所需的內存大小,並將其對齊到對象的最小對齊方式。然後它會從內存池中查找可用的、足夠大的內存塊,如果找到了則將這個內存塊標記爲正在使用,並返回一個指向該內存塊起始地址的指針;如果沒有找到合適的內存塊,則會向操作系統請求一塊新的內存,並返回指向該內存起始地址的指針。

此外,newobject 函數還會設置對象的類型信息和分配相關的元數據,並對內存塊進行清零操作,以確保新分配的對象的所有字段和元素都被初始化爲零值。這樣可以保證程序不會訪問到未初始化的內存,避免因此出現未知的錯誤。

總之,newobject 函數是 Go 語言運行時中的內存分配函數之一,爲創建新的對象提供了方便、高效、安全的支持。

reflect_unsafe_New

reflect_unsafe_New 這個 func 的作用是用於創建一個新的指向類型 t 的未初始化的指針。這個函數是在運行時中被調用的,通常是由反射庫調用的。它允許代碼在運行時動態地創建新的值並初始化它們,而不需要在編譯時硬編碼類型信息。

使用這個函數創建的值是未初始化的,因此必須根據它的類型來初始化它。這可以通過 TypeOf、Elem 和其他反射函數實現。

這個函數的實現使用了類似於 C 中的 malloc 函數的機制來分配內存。首先它計算出所請求的內存大小,然後它檢查是否已經超出了堆分配器限制,最後它請求調用 mheap.alloc 函數來分配內存。然後它將指針轉換爲 unsafe.Pointer 類型並返回。

需要注意的是,這個函數是一個低級函數,應該謹慎使用。它可能會導致內存泄漏和其他問題,因此應該優先考慮使用更高級別的接口,例如 make 和 new 函數。

reflectlite_unsafe_New

該函數 reflectlite_unsafe_New 是用於創建一個指定類型的空對象,該對象不包含任何字段或其它數據的結構體。該函數的功能類似於 golang 中的 new 函數,但是在性能上更加高效,因爲它使用了 unsafe 包,可以直接操作內存,而不需要通過 golang 的對象初始化機制。

在 runtime 中,malloc.go 是用於實現內存分配的代碼。在該文件中,提供了一些基礎的內存分配函數,如 mallocgc、malloc 和 calloc 等等。同時還提供了一些輔助函數,如設置內存對齊的函數 memalign 和計算內存頁大小的函數 pagesize。

reflectlite_unsafe_New 函數是在 malloc.go 中創建的,通過使用 unsafe 包提供的指針操作,通過調用 runtime_rawmem 可以創建一個指定類型的空對象。這個空對象包含了它所代表的類型信息,但是不包含具體的字段數據。通常情況下,我們可以在這個空對象的基礎上,使用 golang 中的各種函數和方法來初始化具體的字段數據,以達到定製化的需求。

newarray

在 Go 語言中,newarray 函數是用來創建數組的。它是 runtime 包中的一個函數,通過用於分配新的數組的底層機制,將指定類型的數組以及元素數量傳遞給參數,並返回一個指向新分配的數組的指針。以下是它的函數簽名:

func newarray(t *rtype, nel int) unsafe.Pointer {
    ...    
}

參數 t 是元素類型的反射信息,nel 是要分配的數組的元素數量。函數返回一個指向新分配的數組的指針。

newarray 函數內部會調用 mallocgc 函數來動態分配內存空間。這個函數將創建新的數組結構,然後在堆上分配足夠的空間來保存該數組的元素。這個函數返回的指針指向了實際的數組數據。

newarray 函數還包含一些特殊的邏輯,用於處理不同類型的數組。例如,對於字符串數組,它將分配足夠的內存來存儲每個字符串的指針和每個字符串的長度,然後將這些指針和長度存儲在新分配的數組中。

newarray 函數是非常重要的,因爲它允許我們在 Go 語言中創建動態數組。這是創建許多高級數據結構的必要步驟,例如列表,棧和隊列。此外,在 Go 語言中,所有的數組都是動態分配的,而不是靜態的,因此 newarray 函數是實現動態數組的必要工具。

reflect_unsafe_NewArray

reflect_unsafe_NewArray 函數是 Go 語言標準庫中 runtime 包中 malloc.go 文件中的一個函數,它的作用是用於爲任意類型創建指定長度的數組,並返回數組的指針。該函數的實現非常關鍵,因爲它是 Go 語言中動態創建數組的基礎。

在 Go 語言中,reflect_unsafe_NewArray 函數實現了一個內部的調用(allocateType 方法),該方法使用傳入的類型和長度來計算數組的總大小,並調用 mallocgc 函數爲數組分配內存。當該函數被調用時,它會檢查類型 t(表示數組的元素類型)是否是 reflect.SliceHeader 類型,如果是,則允許該函數直接返回一個指向 reflect.SliceHeader 的指針,以便儘可能地將內存分配解除多餘的工作。

該函數的具體代碼實現可以參考 Go 語言標準庫 runtime 包中 malloc.go 文件的源代碼。這個函數在底層實現中使用了很多的 unsafe 包的操作,是比較複雜的實現邏輯。通過該函數,我們可以根據任意類型和長度來創建動態數組,並在程序運行時進行靈活的內存管理。

profilealloc

在 go 語言中,profilealloc 函數是一個調試和性能分析函數,它用於分析內存分配的情況。該函數使用的是當前線程堆空間的分配情況來生成一個分配記錄。這個函數在 runtime 的 malloc.go 文件中定義,在調用中會使用 gopark 函數使當前線程進入休眠狀態,然後生成分配記錄。該記錄包含當前線程的堆內存分配情況,標識線程所在的堆區域,以及分配的大小等信息。

這個功能主要用於分析應用程序的堆內存分配情況,幫助開發者找出內存泄漏和性能瓶頸等問題。通過 profilealloc 函數可以實時監控內存的分配情況,同時還可以輸出分析報告,幫助開發者快速定位問題所在。該函數在調試和優化代碼時非常有用,尤其是在處理大量數據時,可以幫助開發者快速找到內存泄漏和性能瓶頸,提高代碼質量和性能表現。

nextSample

在 Go 語言中,內存分配是由運行時庫 (runtime) 負責的。malloc.go 這個文件是 runtime 的一部分,它具體實現了 Go 語言中的內存分配。而 nextSample 函數是 malloc.go 中的一個函數,用於管理內存分配的採樣。下面詳細介紹其作用:

在 Go 程序中,內存分配是一個非常重要的操作,對於性能和內存使用情況都有相當的影響。爲了更好地瞭解應用程序的內存使用情況,Go 語言內置了一個內存分析工具 pprof 。在 pprof 中,可以通過訪問調查報告來查看內存使用情況,同時還可以進行進一步的分析。而程序內部,內存分配的採樣也是實現這個功能的重要組成部分。

在 malloc.go 中,nextSample 函數的作用是進行內存分配採樣,記錄內存分配的情況,並根據這些情況生成採樣介質。這些採樣介質被保存在 mcache 和 mheap 中。接下來,預留的額外存儲空間將用於之後的新分配和垃圾回收。

但在實際使用中,由於採樣和內存分配之間存在牴觸,使用過多的採樣會影響內存分配的速度和性能。因此,採樣的數量不能太多,也不能過於頻繁。

所以 nextSample 函數實際上是對採樣過程的一個優化,確保在採樣時能夠保持性能,確保生成有效的採樣介質,並減少採樣時對內存分配本身的干擾,同時也確保內存分配的速度和性能不受影響。

fastexprand

在 Go 語言中,fastexprand 函數是一個快速的僞隨機數生成器。該函數使用多項式計算來生成隨機數。具體而言,它使用 XORSHIFT 算法實現了一個 32 位的線性同餘算法,並使用一個 32 位的乘法多項式來將結果與狀態進行混合。

fastexprand 函數用於生成隨機內存地址,以確保每個分配的內存塊都具有不同的地址。這對於垃圾收集和內存管理非常重要,可以避免分代垃圾收集器發生內存碎片。

當 Go 程序需要分配內存時,會調用 malloc.go 文件中的相關函數來執行分配操作。例如,如果程序使用了 make 函數或 new 函數來創建新的對象,則會調用 mallocgc 函數來分配內存。在分配內存之前,該函數會調用 fastexprand 函數來生成一個隨機數,用於分配新內存的地址。

總體來說,fastexprand 函數是 Go 運行時的一個重要組件,用於支持內存管理和垃圾回收操作。它可以保證分配的內存塊具有唯一的地址,並且具有足夠的隨機性來避免內存碎片。

nextSampleNoFP

nextSampleNoFP 函數是在 Go 語言運行時系統中的 malloc.go 文件中的一個函數,它的主要目的是爲了獲得下一個用於採樣的 PC 值。

在 Go 語言中,程序在運行時會通過 runtime 包來進行內存管理。runtime 包包括了各種函數和結構體,可以對程序的內存分配和釋放等操作進行管理和優化。其中,malloc.go 文件是 runtime 包中的一個核心文件,它定義了在堆上分配和釋放內存的函數。

在這個文件中,nextSampleNoFP 函數主要用於在進行 Go 程序性能分析時,獲得當前的 PC 值。它會在 Go 程序中的每個採樣點處方便地更新並記錄 PC 值。這些採樣點通常會在程序的一些熱點位置中觸發,通過記錄這些位置的 PC 值,可以幫助開發者進行程序的優化。

具體來說,nextSampleNoFP 函數會首先從當前的 PC 值(程序計數器)中獲取一個哈希值,並將其保存到樣本計數器(sampleCounter)中。然後,它會從記錄樣本位置的數組中獲取下一個採樣點的索引值,並將其保存到數組中。最後,函數返回當前的 PC 值,以便於在下一個採樣點處使用。

persistentalloc

在 Go 語言的運行時系統中,persistentalloc 是一個用於分配 persistent memory(持續性內存)的函數,即在進程重啓後也會持久存在的內存。它通過調用 mheap.palloc 函數實現分配內存的功能。

該函數的作用是支持對某些需要持續存儲的數據進行分配和釋放,以滿足程序對持久化數據的需求。比如可以通過該函數分配一塊持久的內存用來存儲程序的配置信息、日誌等。

persistentalloc 的實現源代碼中,主要通過 mheap.palloc 函數分配一塊指定大小的內存,並標記爲不可回收狀態,即進程運行期間保持一直存在。當不需要該塊內存時,可以使用 mheap.pfree 函數主動釋放內存。

在 Go 語言的標準庫中,也提供了與 persistent memory 相關的庫函數,比如 sync.Map 中的 Load 和 Store 方法,就支持將 map 中的數據持久化存儲。同時,在 Go 語言社區中也有一些開源的 persistent memory 庫,如 NVM-Go,讓開發者更加方便地使用和管理 persistent memory。

persistentalloc1

persistentalloc1 是 Go 語言運行時中與內存分配有關的函數之一,其主要作用是分配一個固定大小的內存區域,並將其標記爲不可釋放(也就是 “持久”)。這意味着這個內存區域在生命週期內不會被垃圾回收器回收。

具體來說,persistentalloc1 函數的實現將開始地址和長度以及一個標誌位保存在一個 mheap 結構中的 persistent 字段中,這樣就可以標記這個內存區域成爲 “持久內存”。在以後的內存分配中,如果 persistent 字段中有標記位,則優先嚐試從中分配內存,這樣可以有效減少因爲頻繁分配和釋放而導致的性能損失。

這個函數主要用於一些需要長時間保持的對象,例如 Go 中的 goroutine 調度器、內存池等等,這些對象在整個程序的生命週期內都需要保持內存,不會被釋放。使用 persistentalloc1 可以有效減少程序因爲頻繁分配和釋放內存而導致的性能損失,提高程序的執行效率。

inPersistentAlloc

在 Go 語言中,inPersistentAlloc 函數是在 runtime 包下的 malloc.go 文件中定義的。該函數實現了一種直接從操作系統申請內存的分配方案,被用於在程序運行期間動態分配大量內存時提高性能。inPersistentAlloc 函數的主要作用是使用 mmap 系統調用,在運行時向操作系統請求一塊連續的虛擬地址空間,並在分配的地址空間上進行內存分配。

在調用 inPersistentAlloc 函數時,可以通過傳入一個整數類型的參數 size,該參數指定了需要分配的內存大小。函數返回的是一個 unsafe.Pointer 類型的指針,該指針指向分配的內存塊的起始地址。在函數調用結束後,分配的內存塊可以被程序員使用,並可以隨時訪問其中的數據。

需要注意的是,由於 inPersistentAlloc 實現的是直接從操作系統申請內存的分配方式,因此使用該函數分配的內存塊不會被 Go 自動垃圾回收處理。程序員需要手動釋放所分配的全部內存,以避免出現內存泄漏的問題。

init

go/src/runtime 目錄中的 malloc.go 文件中,有一個 init 函數,它的作用是在 runtime 包被導入時進行一些初始化工作。

init 函數主要完成以下工作:

  1. 初始化 smallSizeMaxClasslargeSizeMaxClass 數組

這些數組包含了從 8 字節到 1<<30(1GB)大的堆塊的大小,以及對應的 span 的 class 大小。這些數組用於計算申請內存時需要的 class 大小。

  1. 初始化 mheapmemstats 變量

mheap 變量代表了堆內存的管理器,用於分配和釋放內存。memstats 變量包含了有關內存使用情況和內存分配器性能的統計信息。

  1. 初始化 central 等變量

central 代表了中央堆,用於分配較大的對象。tinytinyNoSplit 代表了用於分配小對象的 tcache。spanAlloc 等變量代表了用於分配 span 在內存池中的對象。

  1. 註冊 semaevent 變量

在分配和釋放內存時使用的信號量。

總的來說,init 函數初始化了各種數據結構和變量,爲內存分配器的正常工作準備了必要的條件。

alloc

在 Go 語言中,malloc.go 文件中的 alloc 函數用於爲堆分配器分配內存。

具體來說,alloc 函數的作用是:

  1. 從自由列表(free list)中尋找合適大小的空閒區塊,如果存在,則直接分配;否則進入下一步。

  2. 從堆空間中分配新的空閒區塊,如果分配成功,則返回;否則執行下一步。

  3. 如果分配失敗,調用 GC 進行垃圾回收,並判斷是否需要擴展堆空間。如果需要擴展堆空間,則調用 sysAlloc 分配更多的物理內存,並將其添加到堆中,然後再次嘗試分配。

值得注意的是,alloc 函數還需要處理內存對齊等細節問題,以確保分配的內存區塊滿足應用程序的需求。同時,該函數還要記錄分配的內存總量和內存分配次數等統計信息,以用於後續的性能分析和測試。

總之,alloc 函數在堆分配器中扮演着至關重要的角色,其效率和穩定性直接影響着 Go 程序的性能和可靠性。

add

add 這個 func 的作用是將一段連續的空閒內存塊加入到空閒鏈表中。

具體地說,當一個內存塊被釋放時,會通過 add 函數將其加入到一個空閒鏈表中,以便之後的內存分配能夠使用它。add 函數會根據內存塊的大小將其分配到相應的空閒鏈表中,這樣可以使得分配更爲高效。另外,add 函數還會檢查是否有相鄰的空閒內存塊,如果有的話還會將它們合併起來,以提高內存利用率。

總之,add 函數是用來管理內存塊的空閒鏈表的,它會將內存塊分配到相應的鏈表中,並且能夠合併相鄰的空閒內存塊,實現更高效的內存管理。

computeRZlog

在 go 語言中,malloc.go 文件是與內存分配(即動態分配和回收)相關的核心文件之一。其中的 computeRZlog 函數用於計算並返回虛擬地址空間中 z 的對數值(即 log2(z))。由於在內存分配時需要對內存進行對齊,因此計算 z 的值非常重要。

具體來說,computeRZlog 函數的作用如下:

  1. 確定可用空間:通過計算計算 z 的對數值,可以確定可用空間的大小。

  2. 確定對齊方式:計算 z 的值還可以確定用於對齊的位數(即 2^z),因此可以確定對齊方式。

  3. 減少內存浪費:通過使用適當的對齊方式,可以最大程度地減少內存浪費,提高系統性能和效率。

總之,computeRZlog 函數在 go 語言的內存分配過程中起着重要的作用,可以幫助系統有效地管理內存,提高系統性能和效率。

內容由 chatgpt 生成,倉庫地址:https://github.com/cuishuang/explain-source-code-by-chatgpt

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/UmKrdXrO_NjFFliTa68qXw