背景
go的很多东西是内敛的,提倡大道至简,但是,我们也看到过对于一些对性能要求比较高的业务场景(比如游戏的某些业务场景),使用cgo等技术手段来绕过go的垃圾回收。所以说,我们不要把所有的目光都盯到那95%上,依然还有5%的情况是需要我们来处理的。即便在今天来看,内存依然是非常紧缺的资源,那么我们可以想象一下,如何管理和分配内存资源,这是一个非常有挑战的话题。
下面我们来看一下这段代码,看看内存究竟发生来什么。
package main
func A() *int {
x := 100
return &x
}
但是如果有c语言经验的人,会知道这个代码是有问题的,我们来简单测试一下这个代码。
package main
import "testing"
func BenchmarkA (b *testing.B){
for i := 0; i < b.N; i++{
A()
}
}
执行go test -v -test.bench . -benchmem
之后,测试结果如下:
goos: darwin
goarch: amd64
pkg: golang/runtime
BenchmarkA-4 2000000000 0.32 ns/op 0 B/op 0 allocs/op
PASS
ok golang/runtime 0.739s
感觉没有什么问题,只是做性能测试,那么我在不修改参数的情况下,加一个参数,禁止函数内联,我们再进行一下测试
执行go test -v -test.bench . -benchmem -gcflags -l
,继续执行,测试情况如下:
goos: darwin
goarch: amd64
pkg: golang/runtime
BenchmarkA-4 100000000 15.8 ns/op 8 B/op 1 allocs/op
PASS
ok golang/runtime 1.646s
我们可以看到性能下降的很严重,关键是,它每次执行都会在内存上进行一次
分配, 64位系统的是8字节(整数指针),很显然,go对于内存分配的这种行为是由运行时和编译器来决定的,跟我们写的代码无关,也就是当我们写下这段代码的时候,我们并不能保证我们的变量是分配在栈上还是堆上的。
应用系统可能有时候不太关心,但是我们可以想象一下,每秒钟会在内存中分配几十万个临时对象,这几十万个临时对象会对GC造成多大的压力。因为有时候垃圾回收并不在乎你这个对象是大是小,而在乎的是你这个对象有多少个,为什么?因为他需要检查你的每个对象究竟是不是可达的,他需要把所有可达对象保留下来,把所有不可达对象回收了。也就是说,在单位时间内,堆上的临时对象越多,垃圾回收的压力越大。小小的一个控制开关,会导致你的性能有很大的影响。内存在栈上分配,变成了在堆上分配。
内存分配器由来
内存单元
初始化和基本架构
内存分配
内存回收
物理内存释放
有疑问加站长微信联系(非本文作者)