入门20_阅读go语言进阶

go语言进阶:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUzNTY5MzU2MA==&action=getalbum&album_id=1688588073602727943

1. 图解Go内存管理器的内存分配策略

2. 解密Go协程的栈内存管理

3. 图文结合,白话Go的垃圾回收原理

4. Go指针的使用限制和突破之路

指针的限制
限制一:指针不能参与运算
限制二:不同类型的指针不允许相互转换。
限制三:不同类型的指针不能比较和相互赋值
unsafe 包
unsafe 包用于编译阶段可以绕过 Go 语言的类型系统,直接操作内存。例如,利用 unsafe 包操作一个结构体的未导出成员。unsafe 包让我可以直接读写内存的能力。

unsafe.Pointer
unsafe.Pointer称为通用指针,官方文档对该类型有四个重要描述:

任何类型的指针都可以被转化为Pointer
Pointer可以被转化为任何类型的指针
uintptr可以被转化为Pointer
Pointer可以被转化为uintptr
unsafe.Pointer 不能直接进行数学运算,但可以把它转换成 uintptr,对 uintptr 类型进行数学运算,再转换成 unsafe.Pointer 类型。

1
2
// uintptr、unsafe.Pointer和普通指针之间的转换关系
uintptr <==> unsafe.Pointer <==> *T

uintptr
uintptr是 Go 语言的内置类型,是能存储指针的整型,在64位平台上底层的数据类型是 uint64。

5. 内存对齐 | 原来字段顺序还能影响结构体占用的内存空间

内存对齐的原则是:将数据尽量的存储在一个字长内,避免跨字长的存储。
零字节类型的对齐
我们都知道 struct{} 类型占用的字节数是 0,但其实它的内存对齐数是 1,这么设定的原因为了保证当它作为结构体的末尾字段时,不会访问到其他数据结构的地址。比如像下面这个结构体 ST2

1
2
3
4
5
type ST1 struct {
A byte
B int64
C byte
}

del01

1
2
3
4
5
type ST1 struct {
A byte
C byte
B int64
}

del01
仅仅只是调换了一下顺序,结构体 ST1 就减少了三分之一的内存占用空间。在实际编程应用时大部分时候我们不用太过于注意内存对齐对数据结构空间的影响,不过作为工程师了解内存对齐这个知识还是很重要的,它实际上是一种典型的以空间换时间的策略。

6. 内联函数和编译器对Go代码的优化

内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但在选择使用内联函数时,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支。另外还需要特别注意的是对递归函数的内联扩展可能引起部分编译器的无穷编译。
Note:内联优化一般用于能够快速执行的函数,因为在这种情况下函数调用的时间消耗显得更为突出,同时内联体量小的函数也不会明显增加编译后的执行文件占用的空间。

告诉编译器不对函数进行内联

1
2
3
4
5
// 注意下面这行注释,"//"后面不要有空格
//go:noinline
func add(x int, y int) int {
return x + y
}

查看哪些被内联
可以通过给 go build 命令传递 -gcflags -m 参数

哪些函数不会被内联
函数体内包含:闭包调用,select ,for ,defer,go 关键字的的函数不会进行内联。并且除了这些,还有其它的限制。当解析AST时,Go申请了80个节点作为内联的预算。每个节点都会消耗一个预算。比如,a = a + 1这行代码包含了5个节点:AS, NAME, ADD, NAME, LITERAL。
当一个函数的开销超过了这个预算,就无法内联。

7 Go内存管理之代码的逃逸分析

Q:如何得知变量是分配在栈(stack)上还是堆(heap)上?
A: 准确地说,你并不需要知道。Golang 中的变量只要被引用就一直会存活,存储在堆上还是栈上由内部实现决定而和具体的语法没有关系。知道变量的存储位置确实对程序的效率有帮助。如果可能,Golang 编译器会将函数的局部变量分配到函数栈帧(stack frame)上。然而,如果编译器不能确保变量在函数 return 之后不再被引用,编译器就会将变量分配到堆上。而且,如果一个局部变量非常大,那么它也应该被分配到堆上而不是栈上。当前情况下,如果一个变量被取地址,那么它就有可能被分配到堆上。然而,还要对这些变量做逃逸分析,如果函数 return 之后,变量不再被引用,则将其分配到栈上。
Go编译器决定变量应该分配到什么地方时会进行逃逸分析。
逃逸分析指的是:Go编译器会跨越函数和包的边界进行全局的逃逸分析。它会检查是否需要在堆上为一个变量分配内存,还是说可以在栈本身的内存里对其进行管理。

8. 什么是大端序和小端序,为什么要有字节序

9. Golang atomic.Value 的前世今生

10. Goroutine Local Storage的一些实现方案和必要性讨论

11. Go语言的IO库那么多,我该怎么选?

12. Context是怎么在Go语言中发挥关键作用的

13. 用手写一个工具的过程讲清楚Go反射的使用方法和应用场景

14. 盘点一下结构体标签在Go中的应用

15. 学会这几招让 Go 程序自己监控自己

获取Go进程的资源使用情况使用gopstuil库即可完成,它我们屏蔽了各个系统之间的差异,帮助我们方便地获取各种系统和硬件信息。gopsutil将不同的功能划分到不同的子包中,它提供的模块主要有:

1
2
3
4
5
6
7
cpu:系统CPU 相关模块;
disk:系统磁盘相关模块;
docker:docker 相关模块;
mem:内存相关模块;
net:网络相关;
process:进程相关模块;
winservices:Windows 服务相关模块。

16. Go 服务进行自动采样性能分析的方案设计与实现

开源的自动采样库:社区里其实已经有开源库实现了类似的功能,比如曹大在蚂蚁的时候设计的Holmes ,其实曹大在桃花源公众号里发文章分享过。
无人值守的自动 dump(一)
无人值守的自动 dump(二)
使用起来也比较方便,比如下面是一个对内存使用率突增 25% 和超过阈值 80% 这两种情况下让程序自动进行Mem信息采样的例子。如果你公司里的基建还没有到把持续采样做到统一平台里的水平的话,Holmes 是一个比较好的选择,能快速解决问题,比较适合中小型的业务快速发展的团队

17. Go 函数的 Map 型参数,会发生扩容后指向不同底层内存的事儿吗?

“如果把 Map 作为函数参数传递,会不会像用 Slice 做参数时一样诡异,是不是一定要把 Map 当成返回值返回才能让函数外部的 Map 变量看到这里添加的数据”?
del01
Map在运行时实际上是一个 hmap类型的指针,只不过在我们写代码阶段被隐藏起来了。
既然是一个 Map 类型的变量实际上是一个指针变量,这跟 Slice 就完全不同了,虽然指针作为函数参数时在 Go 里面也是按照值传递的,但是内外两个指针是指向的同一个 hamp 结构所在的内存,hmap 结构里有很多字段,回答这里的问题,我们只需要知道 buckets 和 oldbuckets 这两个指针类型的字段就行了。
虽然扩容导致 Map 有了新 bucket 数组的地址,但是这个地址是存在 hmap 的字段 buckets 上的,变更字段的值并不会影响 hmap 本身的内存地址

18. 如何在 Go 函数中获取调用者的函数名、文件名、行号

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×