上一篇提到的关于gc性能的问题,对比slice和map的结构可以看出为了存储数据map用了更多的内存空间,并且可能存在链表,链表的每个节点在gc时都做为一个小对象对待,增加了扫描的时间,因此gc时间相对更长。 slice初始化与复制slice通过内部指针和相关属性引用数组片段,来实现变长方案。实现方式和数据结构都类似C++中的vector。它本身是结构体,作为参数传递时传递的是slice本身而不是它引用的底层数组。len()可获得slice长度,cap()可获得slice容量。 12345typ...
-
Go Slice与String内存布局和实现
-
Go语言黑魔法
今天我要教大家一些无用技能,也可以叫它奇技淫巧或者黑魔法。用得好可以提升性能,用得不好就会招来恶魔,嘿嘿。黑魔法导论为了让大家在学习了基础黑魔法之后能有所悟,在必要的时候能创造出本文传授之外的属于自己的魔法,这里需要先给大家打好基础。学习Go语言黑魔法之前,需要先看清Go世界的本质,你才能获得像Neo一样的能力。在Go语言中,Slice本质是什么呢?是一个reflect.SliceHeader结构体和这个结构体中Data字段所指向的内存。String本质是什么呢?是一个reflect.Strin...
-
Go Hashmap内存布局和实现
想了解Go内置类型的内存布局的契机,是一次在调试“不同类型的小对象频繁创建对gc性能的影响”时发现map的gc性能不佳,而作为对比的包含slice的struct却很好。这里总结Go runtime里map的实现,可以解释这个问题。 hash table内部结构Go的map就是hashmap,源码在src/runtime/hashmap.go。对比C++用红黑树实现的map,Go的map是unordered map,即无法对key值排序遍历。跟传统的hashmap的实现方法一样,它通过一个buck...
-
Go 内存管理
内存管理缓存结构Go实现的内存管理采用了tcmalloc这种架构,并配合goroutine和垃圾回收。tcmalloc的基本策略就是将内存分为多个级别。申请对象优先从最小级别的内存管理集合mcache中获取,若mcache无法命中则需要向mcentral申请一批内存块缓存到本地mcache中,若mcentral无空闲的内存块,则向mheap申请来填充mcentral,最后向系统申请。 mcache + mspan最小级别的内存块管理集合mcache由goroutine自己维护,这样从中申请内存不...
-
golang 内存分析/动态追踪
golang pprof 当你的golang程序在运行过程中消耗了超出你理解的内存时,你就需要搞明白,到底是 程序中哪些代码导致了这些内存消耗。此时golang编译好的程序对你来说是个黑盒,该 如何搞清其中的内存使用呢?幸好golang已经内置了一些机制来帮助我们进行分析和追 踪。 此时,通常我们可以采用golang的pprof来帮助我们分析golang进程的内存使用。 pprof 实例 通常我们采用http api来将pprof信息暴露出来以供分析,我们可以采用net/http/pprof 这...
-
golang 汇编
在某些场景下,我们需要进行一些特殊优化,因此我们可能需要用到golang汇编,golang汇编源于plan9,此方面的 介绍很多,就不进行展开了。我们WHY和HOW开始讲起。 golang汇编相关的内容还是很少的,而且多数都语焉不详,而且缺乏细节。对于之前没有汇编经验的人来说,是很难 理解的。而且很多资料都过时了,包括官方文档的一些细节也未及时更新。因此需要掌握该知识的人需要仔细揣摩, 反复实验。 WHY 我们为什么需要用到golang的汇编,基本出于以下场景。 算法加速,golang编译器生成...
-
Golang网络:核心API实现剖析二)
---- *说明* ---- 前面的章节我们基本聊完了golang网络编程的关键API流程,但遗留了一个关键内容:当系统调用返回EAGAIN时,会调用WaitRead/WaitWrite来阻塞当前协程,现在我们接着聊。 -------------------- *WaitRead/WaitWrite* -------------------- func (pd *pollDesc) Wait(mode int) error { res := runtime_pollWait(pd.runt...
-
Golang网络:核心API实现剖析(一)
这一章节我们将详细描述网络关键API的实现,主要包括Listen、Accept、Read、Write等。 另外,为了突出关键流程,我们选择忽略所有的错误。这样可以使得代码看起来更为简单。 而且我们只关注tcp协议实现,udp和unix socket不是我们关心的。 -------- *Listen* -------- func Listen(net, laddr string) (Listener, error) { la, err := resolveAddr("listen", net,...
-
Golang socket数据结构
------ *编程接口* ------ func Listen(net, laddr string) (Listener, error) func (*TCPListener) Accept (c Conn, err error) func (c *conn) Read(b []byte) (int, error) func (c *conn) Write(b []byte) (int, error) -------- *内部数据结构* -------- *Listener与TCPLis...
-
Golang协程调度的数据结构
Golang中实现协程调度算法的主要有以下三个数据结构,正是这三个结构加上一些算法构成了Golang的协程调度算法,当然,这些数据结构也是在不断进化的,保不准未来又会加入其他结构来提升调度器性能。 <img src="https://pic2.zhimg.com/v2-493d59c83641f785b09db951249ef019_b.jpg" data-rawwidth="431" data-rawheight="128" class="origin_image zh-lightbox-t...
-
协程调度时机三:抢占式调度
---- *说明* ---- 虽然我们一直强调golang调度器是非抢占式。非抢占式的一个最大坏处是无法保证公平性,如果一个g处于死循环状态,那么其他协程可能就会被饿死。 所幸的是,Golang在1.4版本中加入了抢占式调度的逻辑,抢占式调度必然可能在g执行的某个时刻被剥夺cpu,让给其他协程。 ---- *实现* ---- 还记得我们之前说过Golang的sysmon协程么,该协程会定期唤醒作系统状态检查,我们前面说过了它如何检查处于Psyscall状态的p,以便让处于系统调用状态的P可...
-
协程调度时机二:Channel读写
---- *说明* ---- Golang中channle是协程间信息交互的主要手段。Golang的channel分为有缓冲和无缓冲两种,关于他们之间的用法区别可以自行google,这里不再赘述。Golang中的channel读写均是同步语义,写满的、读空的channel都会触发协程调度。 ------------- *向channel写数据* ------------- 无论是无缓冲还是有缓冲channel,当向channel写数据发现channel已满时,都需要将当前写的协程挂起,并进...
-
协程调度时机一:系统调用
---- *前言* ---- 在讲述系统调用发生的协程调度之前,让我们看看go是如何进入系统调用的,理解了这个让我们不会对后面所说的一些东西感到很陌生。 golang对操作系统的系统调用作了封装,提供了syscall这样的库让我们执行系统调用。例如,Read系统调用实现如下: func Read(fd int, p []byte) (n int, err error) { n, err = read(fd, p) if raceenabled { if n > 0 { ...... } ....
-
Golang协程调度二:协程切换原理
---- *概述* ---- 协程是Golang中的轻量级线程,麻雀虽小五脏俱全,Golang管理协程时也必然会涉及到协程之间的切换:阻塞的协程被切换出去,可运行的协程被切换进来。我们在本章节就来仔细分析下协程如何切换。 *TLS* thread local storage: *getg()* goget()用来获取当前线程正在执行的协程g。该协程g被存储在TLS中。 *mcall()* mcall在golang需要进行协程切换时被调用,用来保存被切换出去协程的信息,并在当前线程的...
-
Golang协程调度(一):协程状态
---- 状态总览 ---- 在讲解操作系统进程调度的部分时,几乎所有的书籍都会先列出一张进程的状态迁移图,通过状态图,能很清晰的把进程调度的每个环节串联起来,方便理解。 Go运行时的调度器其实可以看成OS调度器的某种简化版 本,一个goroutine在其生命周期之中,同样包含了各种状态的变换。弄清了这些状态及状态间切换的原理,对搞清整个Go调度器会非常有帮助。 以下是我总结的一张goroutine的状态迁移图,圆形框表示状态,箭头及文字信息表示切换的方向和条件: <img src="h...
-
协程栈扩容
------- 协程栈详细布局 ------- 我们前面说到,在创建一个协程时就为其创建了一个初始的栈,用来执行函数调用。协程栈的大概布局情况如下: <img src="https://pic4.zhimg.com/v2-db96088c66d6cfe990b439f99ef0463b_b.png" data-rawwidth="1250" data-rawheight="718" class="origin_image zh-lightbox-thumb" width="1250" dat...
-
Golang协程栈初始化
------ 主协程初始化 ------ Golang的主协程指的是运行main函数的协程,而子协程指的是在程序运行过程中由主协程创建的协程。每个线程(m)只会有一个主协程,而子协程可能会有很多很多。 子协程和主协程在概念和内部实现上几乎没有任何区别,唯一的不同在于它们的初始栈大小不同。 我们先看看测试过程中生成的主协程堆栈示例。我测试代码中就生成了一个主协程,通过反汇编代码看到他的样子大概如下: <img src="https://pic1.zhimg.com/v2-bb4eee1f6...
-
Golang协程栈概述
-- 说明 -- 计算机中的栈一个很大的应用场合使用在函数调用中。我们这里简单说说golang的协程栈布局,学过计算机的应该都不会陌生。 ---- 程序事例 ---- package main func f(a, b int) int { sum := 0 sum = a + b for i := 0; i < 1000; i++ { println("sum is:", sum) } return sum } func main() { f(1, 2) } ---- 汇编代码 ----...
-
可视化 Go 语言中的并发
本文作者提供了在 2016 的 GopherCon 上的关于 Go 并发可视化的[主题演讲视频](https://www.youtube.com/watch?v=KyuFeiG3Y60)。 Go 语言一个鲜明的优点就是内置的基于 [CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes) 的并发实现。Go 可以说是一个为了并发而设计的语言,允许我们使用它构建复杂的并发流水线。但是开发者是否在脑海中想象过不同...
-
Go 垃圾回收
通常C++通过指针引用计数来回收对象,但是这不能处理循环引用。为了避免引用计数的缺陷,后来出现了标记清除,分代等垃圾回收算法。Go的垃圾回收官方形容为 非分代 非紧缩 写屏障 并发标记清理。标记清理算法的字面解释,就是将可达的内存块进行标记mark,最后没有标记的不可达内存块将进行清理sweep。 三色标记法判断一个对象是不是垃圾需不需要标记,就看是否能从当前栈或全局数据区 直接或间接的引用到这个对象。这个初始的当前goroutine的栈和全局数据区称为GC的root区。扫描从这里开始,通过ma...
-
go语言死循环分析
最近看了一篇文章,如何定位 golang 进程 hang 死的 bug,里面有这样一段代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445package mainimport ( "fmt" "io" "log" "net/http" "runtime" "time")func main() { runtime.GOMAXPROCS(runtime.NumCPU()) go se...
-
如何定位 golang 进程 hang 死的 bug
之前在 golang 群里有人问过为什么程序会莫名其妙的 hang 死然后不再响应任何请求。单核 cpu 打满。 这个特征和我们公司的某个系统曾经遇到的情况很相似,内部经过了很长时间的定位分析总结,期间还各种阅读 golang 的 runtime 和 gc 代码,最终才定位到是业务里出现了类型下面这样的代码: package main import "runtime" func main() { var ch = make(chan int, 100) go func() { for i := ...