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

詳解Go中如何進行進行內(nèi)存優(yōu)化和垃圾收集器管理

 更新時間:2023年11月21日 08:15:47   作者:happyEnding  
這篇文章主要為大家詳細介紹了Go中如何進行進行內(nèi)存優(yōu)化和垃圾收集器管理,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解下

Go 中的棧和堆

這篇文章不會深入研究垃圾收集器的內(nèi)部工作原理,因為大量文章和官方文檔已經(jīng)涵蓋了這個主題。除此之外,我將介紹關(guān)鍵概念來闡明本文探討的主題。

在Go中,數(shù)據(jù)可以分為兩種主要的內(nèi)存存儲:棧和堆。

一般來說,堆棧中存放的數(shù)據(jù)的大小和生命周期是 Go 編譯器可以預(yù)期的。這包括局部函數(shù)變量、函數(shù)參數(shù)、返回值等等。

堆棧是自動管理的,遵循后進先出 (LIFO) 原則。當調(diào)用函數(shù)時,所有關(guān)聯(lián)的數(shù)據(jù)都放置在堆棧頂部,函數(shù)完成后,這些數(shù)據(jù)將被刪除。堆棧高效運行,將內(nèi)存管理的開銷降至最低。在堆棧上檢索和存儲數(shù)據(jù)的過程很快。

盡管如此,并非所有程序數(shù)據(jù)都可以駐留在堆棧中。在執(zhí)行過程中動態(tài)變化的數(shù)據(jù)或需要超出函數(shù)范圍的訪問的數(shù)據(jù)不能容納在堆棧中,因為編譯器無法預(yù)測其使用情況。這些數(shù)據(jù)在堆中找到了自己的家。

與堆棧相比,從堆中檢索數(shù)據(jù)及其管理是更消耗資源的過程。

堆棧與堆分配

正如前面提到的,堆棧提供可預(yù)測大小和壽命的值。此類值的示例包括在函數(shù)內(nèi)聲明的局部變量,如基本數(shù)據(jù)類型(例如數(shù)字和布爾值)、函數(shù)參數(shù)和函數(shù)返回值(如果它們在從函數(shù)返回后不再找到引用)。

Go 編譯器在決定是在堆棧還是堆上分配數(shù)據(jù)時會采用各種細微差別。例如,大小最大為 64 KB 的預(yù)分配切片將分配給堆棧,而超過 64 KB 的切片將指定給堆。同樣的標準適用于數(shù)組:超過 10 MB 的數(shù)組將分派到堆。

為了確定特定變量的分配位置,可以采用逃逸分析。為此,您可以通過使用以下標志從命令行編譯應(yīng)用程序來仔細檢查應(yīng)用程序-gcflags=-m

go build -gcflags=-m main.go

main.go當使用以下標志編譯以下應(yīng)用程序時-gcflags=-m

package main

func main() {
  var arrayBefore10Mb [1310720]int
  arrayBefore10Mb[0] = 1

  var arrayAfter10Mb [1310721]int
  arrayAfter10Mb[0] = 1

  sliceBefore64 := make([]int, 8192)
  sliceOver64 := make([]int, 8193)
  sliceOver64[0] = sliceBefore64[0]
}

結(jié)果表明,arrayAfter10Mb由于數(shù)組大小超過 10 MB,因此該數(shù)組被重新定位到堆中。相反,arrayBefore10Mb保留在堆棧上。此外,sliceBefore64由于其大小小于 64 KB,因此不遷移到堆,而sliceOver64存儲在堆中。

要更深入地了解堆分配,請參閱文檔。

垃圾收集器:管理堆

處理堆的一種有效方法是避免使用它。但是,如果數(shù)據(jù)已經(jīng)進入堆,該怎么辦?

與堆棧相反,堆擁有無限的大小和持續(xù)增長。堆是動態(tài)生成的對象的所在地,例如結(jié)構(gòu)、切片、映射和無法適應(yīng)堆棧約束的大量內(nèi)存塊。

垃圾收集器是回收堆內(nèi)存并防止其完全阻塞的唯一工具。

了解垃圾收集器

垃圾收集器(通常稱為 GC)是一個專用系統(tǒng),旨在識別和釋放動態(tài)分配的內(nèi)存。

Go 采用了一種植根于跟蹤和標記和清除方法的垃圾收集算法。在標記階段,垃圾收集器將應(yīng)用程序主動使用的數(shù)據(jù)指定為活動堆。隨后,在清理階段,GC 會遍歷未標記的內(nèi)存,使其可供重用。

然而,垃圾收集器的操作是有代價的,消耗兩個重要的系統(tǒng)資源:CPU 時間和物理內(nèi)存。

垃圾收集器內(nèi)的內(nèi)存包括:

  • 活動堆內(nèi)存(在上一個垃圾收集周期中標記為“活動”的內(nèi)存)。
  • 新的堆內(nèi)存(尚未被垃圾收集器分析的堆內(nèi)存)。
  • 元數(shù)據(jù)存儲,與前兩個實體相比通常微不足道。

垃圾收集器消耗的 CPU 時間取決于其操作模式。某些垃圾收集器實現(xiàn)(標記為“stop-the-world”)會在垃圾收集期間完全暫停程序執(zhí)行,從而導(dǎo)致 CPU 時間浪費在非生產(chǎn)性任務(wù)上。

在 Go 的上下文中,垃圾收集器并不完全是“停止世界”,它的大部分工作(包括堆標記)與應(yīng)用程序的執(zhí)行并行。然而,它確實需要一些限制,并在一個周期內(nèi)定期停止活動代碼的執(zhí)行。

到現(xiàn)在為止,讓我們更進一步。

管理垃圾收集器

控制 Go 中的垃圾收集器可以通過特定參數(shù)來實現(xiàn):GOGC 環(huán)境變量或其功能等效項 SetGCPercent,可在運行時/調(diào)試包中找到。

GOGC 參數(shù)指示與垃圾收集啟動時的活動內(nèi)存相關(guān)的新的、未分配的堆內(nèi)存的百分比。

默認情況下,GOGC 設(shè)置為 100,表示當新內(nèi)存量達到活動堆內(nèi)存的 100% 時觸發(fā)垃圾回收。

考慮一個示例程序,并通過 go 工具跟蹤跟蹤堆大小的變化。我們將使用 Go 版本 1.20.1 來執(zhí)行該程序。

在此示例中,該performMemoryIntensiveTask函數(shù)消耗了堆中分配的大量內(nèi)存。該函數(shù)啟動一個隊列大小為NumWorker且任務(wù)數(shù)量等于 的工作池NumTasks。

package main

import (
 "fmt"
 "os"
 "runtime/debug"
 "runtime/trace"
 "sync"
)

const (
 NumWorkers    = 4     // Number of workers.
 NumTasks      = 500   // Number of tasks.
 MemoryIntense = 10000 // Size of memory-intensive task (number of elements).
)

func main() {
 // Write to the trace file.
 f, _ := os.Create("trace.out")
 trace.Start(f)
 defer trace.Stop()

 // Set the target percentage for the garbage collector. Default is 100%.
 debug.SetGCPercent(100)

 // Task queue and result queue.
 taskQueue := make(chan int, NumTasks)
 resultQueue := make(chan int, NumTasks)

 // Start workers.
 var wg sync.WaitGroup
 wg.Add(NumWorkers)
 for i := 0; i < NumWorkers; i++ {
  go worker(taskQueue, resultQueue, &wg)
 }

 // Send tasks to the queue.
 for i := 0; i < NumTasks; i++ {
  taskQueue <- i


 }
 close(taskQueue)

 // Retrieve results from the queue.
 go func() {
  wg.Wait()
  close(resultQueue)
 }()

 // Process the results.
 for result := range resultQueue {
  fmt.Println("Result:", result)
 }

 fmt.Println("Done!")
}

// Worker function.
func worker(tasks <-chan int, results chan<- int, wg *sync.WaitGroup) {
 defer wg.Done()

 for task := range tasks {
  result := performMemoryIntensiveTask(task)
  results <- result
 }
}

// performMemoryIntensiveTask is a memory-intensive function.
func performMemoryIntensiveTask(task int) int {
 // Create a large-sized slice.
 data := make([]int, MemoryIntense)
 for i := 0; i < MemoryIntense; i++ {
  data[i] = i + task
 }

 // Imitation of latency.
 time.Sleep(10 * time.Millisecond)

 // Calculate the result.
 result := 0
 for eachValue := range data {
  result += eachValue
 }
 return result
}

為了跟蹤程序的執(zhí)行,結(jié)果被寫入文件trace.out

// Writing to the trace file.
f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()

通過利用go tool trace,我們可以觀察堆大小的波動并分析程序內(nèi)垃圾收集器的行為。

請注意,不同 Go 版本的具體細節(jié)和功能go tool trace可能有所不同,因此建議查閱官方文檔以獲取特定于版本的信息。

GOGC的默認值

debug.SetGCPercentGOGC參數(shù)可以通過包中的函數(shù)設(shè)置runtime/debug。默認情況下,GOGC 配置為 100%。

要運行我們的程序,請使用以下命令:

go run main.go

程序執(zhí)行后,trace.out會生成一個文件。要對其進行分析,請執(zhí)行以下命令:

go tool trace trace.out

當 GOGC 值為 100 時,垃圾收集器被觸發(fā) 16 次,在我們的示例中總共消耗了 14 毫秒。

增加 GC 頻率

如果我們在設(shè)置為 10% 后運行代碼debug.SetGCPercent(10),垃圾收集器會被更頻繁地調(diào)用。在這種情況下,當當前堆大小達到活動堆大小的 10% 時,垃圾收集器將激活。

換句話說,如果活動堆大小為 10 MB,則當當前堆大小達到 1 MB 時,垃圾收集器將啟動。

當 GOGC 值為 10 時,垃圾收集器被調(diào)用 38 次,總垃圾收集時間為 28 ms。

降低 GC 頻率

以 1000% 運行相同的程序debug.SetGCPercent(1000)會導(dǎo)致垃圾收集器在當前堆大小達到活動堆大小的 1000% 時觸發(fā)。

在這種情況下,垃圾收集器被激活一次,執(zhí)行 2 毫秒。

禁用GC

您還可以通過設(shè)置 GOGC=off 或使用來禁用垃圾收集器debug.SetGCPercent(-1).

關(guān)閉 GC 后,應(yīng)用程序中的堆大小會持續(xù)增長,直到程序執(zhí)行為止。

堆內(nèi)存占用

在實時堆的實際內(nèi)存分配中,該過程不會像跟蹤中看到的那樣定期且可預(yù)測地發(fā)生。

活動堆可以隨著每個垃圾收集周期動態(tài)變化,并且在某些條件下,其絕對值可能會出現(xiàn)峰值。

為了模擬這種情況,在具有內(nèi)存限制的容器中運行程序可能會導(dǎo)致內(nèi)存不足 (OOM) 錯誤。

在此示例中,程序在內(nèi)存限制為 10 MB 的容器中運行以進行測試。Dockerfile說明如下:

FROM golang:latest as builder

WORKDIR /src
COPY .

RUN go env -w GO111MODULE=on

RUN go mod vendor
RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -a -installsuffix cgo -o app ./cmd/

FROM golang:latest
WORKDIR /root/
COPY --from=builder /src/app .
EXPOSE 8080
CMD ["./app"]

Docker-compose 的描述是:

version: '3'
services:
 my-app:
   build:
     context: .
     dockerfile: Dockerfile
   ports:
     - 8080:8080
   deploy:
     resources:
       limits:
         memory: 10M

啟動容器會debug.SetGCPercent(1000%)導(dǎo)致 OOM 錯誤:

docker-compose build
docker-compose up

容器崩潰并顯示錯誤代碼 137,指示內(nèi)存不足的情況。

避免 OOM 錯誤

從 Go 1.19 版本開始,Golang 引入了帶有 GOMEMLIMIT 選項的“軟內(nèi)存管理”。此功能使用 GOMEMLIMIT 環(huán)境變量來設(shè)置 Go 運行時可以使用的總體內(nèi)存限制。例如,GOMEMLIMIT = 8MiB,其中 8 MB 是內(nèi)存大小。

該機制旨在解決 OOM 問題。啟用 GOMEMLIMIT 后,會定期調(diào)用垃圾收集器以將堆大小保持在一定限制內(nèi),避免內(nèi)存過載。

成本性能問題

GOMEMLIMIT 是一個功能強大但也是雙刃劍的工具。它可能導(dǎo)致一種稱為“死亡螺旋”的情況。當由于實時堆增長或持續(xù)的 Goroutine 泄漏而導(dǎo)致整體內(nèi)存大小接近 GOMEMLIMIT 時,垃圾收集器將根據(jù)限制不斷調(diào)用。

頻繁的垃圾收集器調(diào)用可能會導(dǎo)致 CPU 使用率增加和程序性能下降。與 OOM 錯誤不同,死亡螺旋很難檢測和修復(fù)。

GOMEMLIMIT 不提供 100% 保證嚴格執(zhí)行內(nèi)存限制,從而允許內(nèi)存利用率超出限制。它還設(shè)置了 CPU 使用率限制,以防止過多的資源消耗。

在哪里申請 GOMEMLIMIT 和 GOGC

GOMEMLIMIT 在多種情況下都具有優(yōu)勢:

  • 在內(nèi)存有限的容器中運行應(yīng)用程序,留下 5-10% 的可用內(nèi)存是一個很好的做法。
  • 在處理資源密集型代碼時,GOMEMLIMIT 的實時管理可能會很有用。
  • 當應(yīng)用程序作為容器中的腳本運行時,禁用垃圾收集器但設(shè)置 GOMEMLIMIT 可以提高性能并防止超出容器的資源限制。

在以下情況下避免使用 GOMEMLIMIT:

  • 當您的程序已經(jīng)接近其操作環(huán)境的內(nèi)存限制時,不要定義內(nèi)存限制。
  • 在您不監(jiān)督的執(zhí)行環(huán)境中部署程序時,請避免實施內(nèi)存限制,特別是當程序的內(nèi)存消耗與其輸入數(shù)據(jù)直接相關(guān)時。這對于命令行界面或桌面應(yīng)用程序等工具尤其重要。

顯然,通過采取故意的方法,我們可以有效地控制特定的程序設(shè)置,包括垃圾收集器和 GOMEMLIMIT。盡管如此,徹底評估實施這些設(shè)置的方法至關(guān)重要。

以上就是詳解Go中如何進行進行內(nèi)存優(yōu)化和垃圾收集器管理的詳細內(nèi)容,更多關(guān)于Go垃圾收集器的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解如何使用Golang擴展Envoy

    詳解如何使用Golang擴展Envoy

    這篇文章主要為大家介紹了詳解如何使用Golang擴展Envoy實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • golang基礎(chǔ)之waitgroup用法以及使用要點

    golang基礎(chǔ)之waitgroup用法以及使用要點

    WaitGroup是Golang并發(fā)的兩種方式之一,一個是Channel,另一個是WaitGroup,下面這篇文章主要給大家介紹了關(guān)于golang基礎(chǔ)之waitgroup用法以及使用要點的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • Go語言Gin框架獲取請求參數(shù)的兩種方式

    Go語言Gin框架獲取請求參數(shù)的兩種方式

    在添加路由處理函數(shù)之后,就可以在路由處理函數(shù)中編寫業(yè)務(wù)處理代碼了,而編寫業(yè)務(wù)代碼第一件事一般就是獲取HTTP請求的參數(shù)吧,Gin框架在net/http包的基礎(chǔ)上封裝了獲取參數(shù)的方式,本文小編給大家介紹了獲取參數(shù)的兩種方式,需要的朋友可以參考下
    2024-01-01
  • go語言中切片與內(nèi)存復(fù)制 memcpy 的實現(xiàn)操作

    go語言中切片與內(nèi)存復(fù)制 memcpy 的實現(xiàn)操作

    這篇文章主要介紹了go語言中切片與內(nèi)存復(fù)制 memcpy 的實現(xiàn)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 詳解簡單高效的Go?struct優(yōu)化

    詳解簡單高效的Go?struct優(yōu)化

    這篇文章主要為大家介紹了簡單高效的Go?struct優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • go語言中的map如何解決散列性能下降

    go語言中的map如何解決散列性能下降

    近期對go語言的map進行深入了解和探究,其中關(guān)于map解決大量沖突的擴容操作設(shè)計的十分巧妙,所以筆者特地整理了這篇文章來探討一下go語言中map如何解決散列性能下降,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下
    2024-03-03
  • 一文詳細談?wù)凣oLang的panic和error

    一文詳細談?wù)凣oLang的panic和error

    說是初識,并不是說第一次使用error和panic包,而是第一次特地去了解golang中的這兩個機制,下面這篇文章主要給大家介紹了關(guān)于如何通過一文詳細談?wù)凣oLang中panic和error的相關(guān)資料,需要的朋友可以參考下
    2022-12-12
  • 淺談Go用于同步和并發(fā)控制的幾種常見鎖

    淺談Go用于同步和并發(fā)控制的幾種常見鎖

    本文主要介紹了淺談Go用于同步和并發(fā)控制的幾種常見鎖,包括互斥鎖、讀寫鎖和一次性鎖等,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-08-08
  • 一文詳解Go語言fmt標準庫的常用占位符使用

    一文詳解Go語言fmt標準庫的常用占位符使用

    這篇文章主要為大家詳細介紹了Go語言中fmt標準庫的常用占位符及其簡單使用,文中的示例代碼講解詳細,對我們學習Go語言有一定的幫助,需要的可以參考一下
    2022-12-12
  • 詳解Golang官方中的一致性哈希組件

    詳解Golang官方中的一致性哈希組件

    這篇文章主要為大家詳細介紹了Golang官方中的一致性哈希組件的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-04-04

最新評論