오늘은 Go의 메모리 관리에 대한 몇 가지 공통 지식을 공유하겠습니다.
# 1. 메모리 할당의 세 가지 주요 구성 요소
Go 메모리 할당 프로세스는 주로 세 가지 주요 구성 요소에 의해 관리됩니다. 위에서 아래까지의 수준은 다음과 같습니다.
mheap
Go 프로그램이 시작되면 먼저 운영 체제에서 대규모 메모리 블록을 적용하고 이를 mheap
구조의 전역 관리에 넘겨줍니다.
구체적으로 관리하는 방법은? mheap은 이 큰 메모리 조각을 서로 다른 사양의 작은 메모리 블록으로 나누는데, 이를 mspan이라고 합니다. 사양의 크기에 따라 약 70가지 유형의 mspan이 있으며 분할은 충족하기에 충분합니다. 다양한 객체 메모리 배포의 필요성.
그러니까 이런 mspan 사양이 크고 작은게 섞여서 관리하기 힘들겠죠?
그래서 다음 단계의 구성 요소인 mcentral이 있습니다.
mcentral
Go 프로그램을 시작하면 많은 mcentral이 초기화되며, 각 mcentral은 mspan의 특정 사양을 관리하는 역할만 담당합니다.
mheap을 기반으로 mspan의 세련된 관리를 구현하는 mcentral과 동일합니다.
하지만 mcentral은 Go 프로그램에서 전역적으로 표시되므로 코루틴이 메모리를 적용하기 위해 mcentral에 올 때마다 잠가야 합니다.
각 코루틴이 메모리를 적용하기 위해 mcentral에 오면 잦은 잠금 및 해제로 인한 오버헤드가 매우 클 것으로 예상할 수 있습니다.
따라서 이 압력을 완화하려면 mcentral의 보조 프록시가 필요합니다
mcache
Go 프로그램에서 각 스레드M
은 프로세서에 바인딩됩니다P, 단일 시간 단위로 하나의 다중 처리 실행만 수행할 수 있습니다고루틴
, 각P
가 바인딩됩니다. 하나는 mcache
의 로컬 캐시. M
会绑定给一个处理器P
,在单一粒度的时间里只能做多处理运行一个goroutine
,每个P
都会绑定一个叫 mcache
的本地缓存。
当需要进行内存分配时,当前运行的goroutine
会从mcache
中查找可用的mspan
。从本地mcache
goroutine
은 사용 가능한 <code style="font-size: 상속;line-height: 상속;overflow-wrap: break-word;padding: mcache에서 2px
4px 찾기 ;경계 반경: 4px;여백-오른쪽: 2px;여백-왼쪽: 2px;색상: rgb(226, 36, 70);배경: rgb(248, 248, 248);">mspan. 로컬mcache
는 메모리 할당 시 잠금을 요구하지 않습니다. 이 할당 전략이 더 효율적입니다. mspan 공급망
mcache의 mspan 수가 항상 충분하지는 않습니다. 공급이 수요를 초과하면 mcache는 mcentral에서 더 많은 mspan을 다시 신청합니다. 마찬가지로 mcentral의 mspan 수가 충분하지 않으면 mcache가 다시 mspan을 신청합니다. 또한 우수한 mheap에서 mspan을 신청할 것입니다. 더 극단적으로 말하면, mheap의 mspan이 프로그램의 메모리 요청을 충족할 수 없는 경우 어떻게 해야 합니까? 그렇다면 다른 방법은 없습니다. mheap은 운영체제의 큰 형님에게 뻔뻔하게 적용할 수 밖에 없습니다.위 공급 프로세스는 메모리 블록이 64KB 미만인 시나리오에만 적용 가능합니다. 그 이유는 Go가 작업자 스레드의 로컬 캐시를 사용하여 해당 메모리 페이지 수(각 페이지 크기는 8KB)를 할당할 수 없기 때문입니다. 프로그램. mcache
和全局中心缓存 mcentral
上管理超过 64KB 的内存分配,所以对于那些超过 64KB 的内存申请,会直接从堆上(mheap
# 2. 힙 메모리와 스택 메모리란 무엇인가요?
다양한 메모리 관리(할당 및 재활용) 방법에 따라 메모리는힙 메모리와 스택 메모리로 나눌 수 있습니다.
그럼 차이점은 무엇인가요?힙 메모리: 메모리 할당자와 가비지 수집기는 재활용을 담당합니다.
스택 메모리: 컴파일러에 의해 자동으로 할당 및 해제됩니다.
프로그램이 실행될 때 여러 개의 스택 메모리가 있을 수 있지만 확실히 힙 메모리는 하나만 있습니다. 각 스택 메모리는 스레드나 코루틴에 의해 독립적으로 점유되므로 스택에서 메모리를 할당할 때 잠금이 필요하지 않으며 스택 메모리는 함수 종료 후 자동으로 재활용되며 성능은 힙 메모리보다 높습니다. 그리고 힙 메모리는 어떻습니까? 여러 스레드나 코루틴이 동시에 힙에서 메모리를 신청할 수 있으므로 힙에서 메모리를 신청하려면 충돌을 피하기 위해 잠금이 필요하며, 힙 메모리는 함수가 끝난 후 GC(가비지 수집)의 개입이 필요합니다. GC 작업 수가 늘어나면 프로그램 성능이 심각하게 저하됩니다.# 3. 이스케이프 분석의 필요성
프로그램 성능을 향상시키기 위해서는 힙에 대한 메모리 할당을 최소화해야 하며, 이는 GC에 대한 부담을 줄일 수 있음을 알 수 있습니다. 변수가 힙에 메모리를 할당할지 스택에 메모리를 할당할지 결정하는 데 있어 전임자들이 몇 가지 규칙을 요약했지만 코딩할 때 이 문제에 항상 주의를 기울이는 것은 프로그래머의 몫이며 프로그래머에 대한 요구 사항이 상당히 높습니다. 다행스럽게도 Go 컴파일러는 탈출 분석 기능도 제공합니다. 탈출 분석을 사용하면 프로그래머가 힙에 할당한 모든 변수를 직접 감지할 수 있습니다(이 현상을 탈출이라고 합니다). 다음 명령어를 실행하는 방법이 있습니다go build -gcflags '-m -l' demo.go # 或者再加个 -m 查看更详细信息 go build -gcflags '-m -m -l' demo.go
# 메모리 할당 위치 규칙
이스케이프 분석 도구를 사용하면 실제로 어떤 변수가 힙에 할당되는지 수동으로 확인할 수 있습니다.
그럼 이 규칙은 무엇인가요?
요약하면 크게 4가지 상황이 나옵니다.
변수의 사용 범위에 따라
변수 유형이 결정되는지 여부에 따라
점유 크기에 따라 변수의
에 따라 변수의 길이가 결정되는 걸까요? 다음은 하나씩 분석하고 검증해 보겠습니다
변수의 사용 범위에 따라
예를 들어, 아래 예func foo() int {
v := 1024
return v
}
func main() {
m := foo()
fmt.Println(m)
}
로그인 후 복사
에서는 func foo() int { v := 1024 return v } func main() { m := foo() fmt.Println(m) }
go build -gcflags '-m -l' 데모.go </code > 이스케이프 분석 결과를 보려면 <code style="font-size: 상속;line-height: 상속;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin -right: 2px ;margin-left: 2px;color: rgb(226, 36, 70); background: rgb(248, 248, 248);">-m
은 이스케이프 분석 정보를 인쇄하는 것입니다. -l
은 인라인 최적화를 비활성화합니다. 분석 결과, v 변수에 대한 이스케이프 명령은 보이지 않아 이스케이프되지 않고 스택에 할당되었음을 알 수 있습니다. $ go build -gcflags '-m -l' demo.go # command-line-arguments ./demo.go:12:13: ... argument does not escape ./demo.go:12:13: m escapes to heap
go build -gcflags '-m -l' demo.go
来查看逃逸分析的结果,其中 -m
是打印逃逸分析的信息,-l
则是禁止内联优化。
从分析的结果我们并没有看到任何关于 v 变量的逃逸说明,说明其并没有逃逸,它是分配在栈上的。
func foo() *int { v := 1024 return &v } func main() { m := foo() fmt.Println(*m) // 1024 }
而如果该变量还需要在函数范围之外使用,如果还在栈上分配,那么当函数返回的时候,该变量指向的内存空间就会被回收,程序势必会报错,因此对于这种变量只能在堆上分配。
比如下边这个例子,返回的是指针
$ go build -gcflags '-m -l' demo.go # command-line-arguments ./demo.go:6:2: moved to heap: v ./demo.go:12:13: ... argument does not escape ./demo.go:12:14: *m escapes to heap
从逃逸分析的结果中可以看到 moved to heap: v
예를 들어 다음 예에서는 포인터를 반환합니다
func foo() []int { a := []int{1,2,3} return a } func main() { b := foo() fmt.Println(b) }
힙으로 이동: v
, v 변수는 힙에서 할당된 메모리입니다. , 위의 시나리오에는 분명한 차이점이 있습니다. 🎜$ go build -gcflags '-m -l' demo.go # command-line-arguments ./demo.go:6:2: moved to heap: v ./demo.go:12:13: ... argument does not escape ./demo.go:12:14: *m escapes to heap
除了返回指针之外,还有其他的几种情况也可归为一类:
第一种情况:返回任意引用型的变量:Slice 和 Map
func foo() []int { a := []int{1,2,3} return a } func main() { b := foo() fmt.Println(b) }
逃逸分析结果
$ go build -gcflags '-m -l' demo.go # command-line-arguments ./demo.go:6:12: []int literal escapes to heap ./demo.go:12:13: ... argument does not escape ./demo.go:12:13: b escapes to heap
第二种情况:在闭包函数中使用外部变量
func Increase() func() int { n := 0 return func() int { n++ return n } } func main() { in := Increase() fmt.Println(in()) // 1 fmt.Println(in()) // 2 }
逃逸分析结果
$ go build -gcflags '-m -l' demo.go # command-line-arguments ./demo.go:6:2: moved to heap: n ./demo.go:7:9: func literal escapes to heap ./demo.go:15:13: ... argument does not escape ./demo.go:15:16: in() escapes to heap
根据变量类型是否确定
在上边例子中,也许你发现了,所有编译输出的最后一行中都是 m escapes to heap
。
奇怪了,为什么 m 会逃逸到堆上?
其实就是因为我们调用了 fmt.Println()
函数,它的定义如下
func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) }
可见其接收的参数类型是 interface{}
,对于这种编译期不能确定其参数的具体类型,编译器会将其分配于堆上。
根据变量的占用大小
最开始的时候,就介绍到,以 64KB 为分界线,我们将内存块分为 小内存块 和 大内存块。
小内存块走常规的 mspan 供应链申请,而大内存块则需要直接向 mheap,在堆区申请。
以下的例子来说明
func foo() { nums1 := make([]int, 8191) // < 64KB for i := 0; i < 8191; i++ { nums1[i] = i } } func bar() { nums2 := make([]int, 8192) // = 64KB for i := 0; i < 8192; i++ { nums2[i] = i } }
给 -gcflags
多加个 -m
可以看到更详细的逃逸分析的结果
$ go build -gcflags '-m -l' demo.go # command-line-arguments ./demo.go:5:15: make([]int, 8191) does not escape ./demo.go:12:15: make([]int, 8192) escapes to heap
那为什么是 64 KB 呢?
我只能说是试出来的 (8191刚好不逃逸,8192刚好逃逸),网上有很多文章千篇一律的说和 ulimit -a
中的 stack size
有关,但经过了解这个值表示的是系统栈的最大限制是 8192 KB,刚好是 8M。
$ ulimit -a -t: cpu time (seconds) unlimited -f: file size (blocks) unlimited -d: data seg size (kbytes) unlimited -s: stack size (kbytes) 8192
我个人实在无法理解这个 8192 (8M) 和 64 KB 是如何对应上的,如果有朋友知道,还请指教一下。
根据变量长度是否确定
由于逃逸分析是在编译期就运行的,而不是在运行时运行的。因此避免有一些不定长的变量可能会很大,而在栈上分配内存失败,Go 会选择把这些变量统一在堆上申请内存,这是一种可以理解的保险的做法。
func foo() { length := 10 arr := make([]int, 0 ,length) // 由于容量是变量,因此不确定,因此在堆上申请 } func bar() { arr := make([]int, 0 ,10) // 由于容量是常量,因此是确定的,因此在栈上申请 }
위 내용은 의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Go에서 함수 수명주기에는 정의, 로드, 연결, 초기화, 호출 및 반환이 포함됩니다. 변수 범위는 함수 수준과 블록 수준으로 구분됩니다. 함수 내의 변수는 내부적으로 표시되지만 블록 내의 변수는 블록 내에서만 표시됩니다. .

Go에서는 정규식을 사용하여 타임스탬프를 일치시킬 수 있습니다. ISO8601 타임스탬프를 일치시키는 데 사용되는 것과 같은 정규식 문자열을 컴파일합니다. ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ . regexp.MatchString 함수를 사용하여 문자열이 정규식과 일치하는지 확인합니다.

Go에서는 gorilla/websocket 패키지를 사용하여 WebSocket 메시지를 보낼 수 있습니다. 특정 단계: WebSocket 연결을 설정합니다. 문자 메시지 보내기: WriteMessage(websocket.TextMessage,[]byte("Message"))를 호출합니다. 바이너리 메시지 보내기: WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})를 호출합니다.

Go와 Go 언어는 서로 다른 특성을 지닌 서로 다른 개체입니다. Go(Golang이라고도 함)는 동시성, 빠른 컴파일 속도, 메모리 관리 및 크로스 플랫폼 이점으로 유명합니다. Go 언어의 단점은 다른 언어에 비해 생태계가 덜 풍부하고 구문이 더 엄격하며 동적 타이핑이 부족하다는 점입니다.

메모리 누수로 인해 파일, 네트워크 연결, 데이터베이스 연결 등 더 이상 사용하지 않는 리소스를 닫는 방식으로 Go 프로그램 메모리가 지속적으로 증가할 수 있습니다. 더 이상 강력하게 참조되지 않는 경우 약한 참조를 사용하여 메모리 누수 및 가비지 수집 대상 개체를 방지합니다. go 코루틴을 사용하면 메모리 누수를 방지하기 위해 종료 시 코루틴 스택 메모리가 자동으로 해제됩니다.

IDE를 사용하여 Go 함수 문서 보기: 함수 이름 위에 커서를 놓습니다. 단축키(GoLand: Ctrl+Q, VSCode: GoExtensionPack 설치 후 F1을 누르고 "Go:ShowDocumentation" 선택)를 누릅니다.

Golang에서 오류 래퍼를 사용하면 원래 오류에 상황별 정보를 추가하여 새로운 오류를 생성할 수 있습니다. 이는 다양한 라이브러리나 구성 요소에서 발생하는 오류 유형을 통합하여 디버깅 및 오류 처리를 단순화하는 데 사용할 수 있습니다. 단계는 다음과 같습니다. error.Wrap 함수를 사용하여 원래 오류를 새 오류로 래핑합니다. 새 오류에는 원래 오류의 상황별 정보가 포함됩니다. fmt.Printf를 사용하면 래핑된 오류를 출력하여 더 많은 컨텍스트와 실행 가능성을 제공할 수 있습니다. 다양한 유형의 오류를 처리할 때 오류 유형을 통합하려면 오류.Wrap 함수를 사용하세요.

단위 테스트 동시 기능은 동시 환경에서 올바른 동작을 보장하는 데 도움이 되므로 매우 중요합니다. 동시 기능을 테스트할 때는 상호 배제, 동기화, 격리와 같은 기본 원칙을 고려해야 합니다. 동시 기능은 경쟁 조건을 시뮬레이션하고, 테스트하고, 결과를 확인하여 단위 테스트할 수 있습니다.
