GODEBUG=gctrace=1 go run *.go
其中 gctrace=1
表示只针对这个进程进行GC追踪
go采用三色标记法,主要是为了提高并发度,这样扫描过程可以拆分为多个阶段,而不用一次扫描全部
黑 根节点扫描完毕,子节点也扫描完毕
灰 根节点扫描完毕,子节点未扫描
白 未扫描
扫描是从 .bss .data goroutine栈开始扫描,最终遍历整个堆上的对象树
标记过程是一个广度优先的遍历过程,扫描节点,将节点的子节点推送到任务队列中,然后递归扫描子叶节点,直到所有工作队列被排空
mark阶段会将白色对象标记,并推入队列中变为灰色
保障了代码描述中对内存的操作顺序,
即不会在编译期被编译器进行调整,也不会在运行时被CPU的乱序执行所打乱
在应用进入 GC 标记阶段前的 stw 阶段,会将全局变量 runtime.writeBarrier.enabled 修改为 true,这时所有的堆上指针修改操作在修改之前便会额外调用 runtime.gcWriteBarrier
由于GC和Go主程序并发执行,所以必须要在扫描时监控内存可能出现的状态改变,所以需要写屏障,所以需要暂停GO主程序(STW)
改方式的基本思想是:对正在被覆盖的对象进行着色,且如果当时栈未扫描完成,则同样对指针进行着色
程序启动会为每个P分配一个 mark worker 来标记内存,负责为进入STW做前期工作
GC会将灰色object标记为黑色,将灰色object所包含的所有指针所指向的地址都标记为灰色,递归这两个步骤,最终对象非黑即白,其中白色object即未被引用且可以被回收,如果object标记为no scan,则递归结束,标记为黑色
todo https://blog.csdn.net/asd1126163471/article/details/124113816
Golang 默认指针是类型安全的,但它有很多限制。Golang 还有非类型安全的指针,这就是 unsafe 包提供的 unsafe.Pointer。在某些情况下,它会使代码更高效,当然,也更危险。unsafe 包用于 Go 编译器,在编译阶段使用。从名字就可以看出来,它是不安全的,官方并不建议使用。Go 语言类型系统是为了安全和效率设计的,有时,安全会导致效率低下。unsafe 包绕过了 Go 的类型系统,达到直接操作内存的目的,使用它有一定的风险性。但是在某些场景下,使用 unsafe 包提供的函数会提升代码的效率,Go 源码中也是大量使用 unsafe 包。
unsafe
包//定义
type ArbitraryType int
type Pointer *ArbitraryType
//函数
func Sizeof(x AribitraryType) uintptr{}
func Offsetof(x AribitraryType) uintptr{}
func Alignof(x AribitraryType) uintptr{}
Pointer
: 指向任意类型,类似于 C 中的 void*
。
Sizeof
: 返回所传类型的大小,指针只返回指针的本身(x64 8byte x86 4byte
),而不会返回所指向的内存大小。
Offsetof
: 返回 struct
成员在内存中的位置,相对于此结构体的头位置,所传参数必须是结构体成员。传入指针,或者结构体本身,会 error
Alignof
: 返回 M,M 是内存对齐时的倍数。
任意指针都可以和 unsafe.Pointer
相互转换。
uintptr
可以和 unsafe.Pointer
相互转换。
综上,
unsafe.Pointer
是不能进行指针运算的,只能先转为uintptr
计算完再转回unsafe.Pointer
,还有一点要注意的是,uintptr
并没有指针的语义,意思就是uintptr
所指向的对象会被 gc。而unsafe.Pointer
有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。
Protocol Buffers
,是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。本文只介绍 syntax = proto3
的协议语法。
.proto | 注释 | C++ | Python | Go | C# |
---|---|---|---|---|---|
double | 定长编码 | double | float | float64 | double |
float | 定长编码 | float | float | float32 | float |
int32 | 变长编码,负数编码效率低,可使用sint32 |
int32 | int | int32 | int |
int64 | 变长编码,负数编码效率低,可使用sint64 |
int64 | int/long | int64 | long |
uint32 | 变长编码 | uint32 | int/long | uint32 | uint |
uint64 | 变长编码 | uint64 | int/long | unit64 | ulong |
sint32 | 变长编码,对负数编码比int32 更有效率 |
int32 | int | int32 | int |
sint64 | 变长编码,对负数编码比int64 更有效率 |
int64 | int/long | int64 | long |
fixed32 | 总是4 字节,如果值大于2^28 比uint32 更有效率 |
uint32 | int/long | uint64 | ulong |
fixed64 | 总是8 字节,如果值大于2^56 比uint64 更有效率 |
uint64 | int/long | uint64 | ulong |
bool | 1或0的变长编码 | bool | boolean | bool | bool |
string | 必须是UTF-8 编码 |
string | str/unicode | string | string |
bytes | 可包含任意的字节顺序 | string | str | []byte | ByteString |
一个 os 线程会有一个给固定大小的内存块(一般是 2MB),用来存储当前线程中调用或挂起函数的内部变量,固定大小的栈对于复杂和深层次递归是不够的,而 Goroutine 会以一个很小的栈(2KB)开始其生命周期,这个栈会动态伸缩,最大能到达 1GB(32位系统是 250M)
os 线程由操作系统内核调用,每过一定时间(毫秒),硬件计时器会中断处理器,并调用一个名为 scheduler 的内建函数,这个函数会挂起当前执行的线程并保存内存中它的寄存器内存,然后检查线程列表并决定下一次执行哪个线程,并从内存中恢复该线程的寄存器信息,恢复该线程的线程并执行,这就是上下文切换,增加了 CPU 的运行周期。而 Go 的 runtime 包含了自身的调度器,和 os 线程不同是,
Goroutine
属于用户级线程由语言支持,调度由语言支持,所有开销会减少很多(相比于内核上下文切换)。