1. > 生活百科 >

golang全局变量 golang全局变量堆栈

(十一)golang 内存分析

编写过C语言程序的肯定知道通过malloc()方法动态申请内存,其中内存分配器使用的是glibc提供的ptmalloc2。 除了glibc,业界比较出名的内存分配器有Google的tcmalloc和Facebook的jemalloc。二者在避免内存碎片和性能上均比glic有比较大的优势,在多线程环境中效果更明显。

Golang中也实现了内存分配器,原理与tcmalloc类似,简单的说就是维护一块大的全局内存,每个线程(Golang中为P)维护一块小的私有内存,私有内存不足再从全局申请。另外,内存分配与GC(垃圾回收)关系密切,所以了解GC前有必要了解内存分配的原理。

为了方便自主管理内存,做法便是先向系统申请一块内存,然后将内存切割成小块,通过一定的内存分配算法管理内存。 以64位系统为例,Golang程序启动时会向系统申请的内存如下图所示:

预申请的内存划分为spans、bitmap、arena三部分。其中arena即为所谓的堆区,应用中需要的内存从这里分配。其中spans和bitmap是为了管理arena区而存在的。

arena的大小为512G,为了方便管理把arena区域划分成一个个的page,每个page为8KB,一共有512GB/8KB个页;

spans区域存放span的指针,每个指针对应一个page,所以span区域的大小为(512GB/8KB)乘以指针大小8byte = 512M

bitmap区域大小也是通过arena计算出来,不过主要用于GC。

span是用于管理arena页的关键数据结构,每个span中包含1个或多个连续页,为了满足小对象分配,span中的一页会划分更小的粒度,而对于大对象比如超过页大小,则通过多页实现。

根据对象大小,划分了一系列class,每个class都代表一个固定大小的对象,以及每个span的大小。如下表所示:

上表中每列含义如下:

class: class ID,每个span结构中都有一个class ID, 表示该span可处理的对象类型

bytes/obj:该class代表对象的字节数

golang全局变量 golang全局变量堆栈golang全局变量 golang全局变量堆栈


bytes/span:每个span占用堆的字节数,也即页数乘以页大小

objects: 每个span可分配的对象个数,也即(bytes/spans)/(bytes/obj)waste

golang全局变量 golang全局变量堆栈golang全局变量 golang全局变量堆栈


bytes: 每个span产生的内存碎片,也即(bytes/spans)%(bytes/obj)上表可见最大的对象是32K大小,超过32K大小的由特殊的class表示,该class ID为0,每个class只包含一个对象。

span是内存管理的基本单位,每个span用于管理特定的class对象, 跟据对象大小,span将一个或多个页拆分成多个块进行管理。src/runtime/mheap.go:mspan定义了其数据结构:

以class 10为例,span和管理的内存如下图所示:

spanclass为10,参照class表可得出npages=1,nelems=56,elemsize为144。其中startAddr是在span初始化时就指定了某个页的地址。allocBits指向一个位图,每位代表一个块是否被分配,本例中有两个块已经被分配,其allocCount也为2。next和prev用于将多个span链接起来,这有利于管理多个span,接下来会进行说明。

有了管理内存的基本单位span,还要有个数据结构来管理span,这个数据结构叫mcentral,各线程需要内存时从mcentral管理的span中申请内存,为了避免多线程申请内存时不断的加锁,Golang为每个线程分配了span的缓存,这个缓存即是cache。src/runtime/mcache.go:mcache定义了cache的数据结构

alloc为mspan的指针数组,数组大小为class总数的2倍。数组中每个元素代表了一种class类型的span列表,每种class类型都有两组span列表,第一组列表中所表示的对象中包含了指针,第二组列表中所表示的对象不含有指针,这么做是为了提高GC扫描性能,对于不包含指针的span列表,没必要去扫描。根据对象是否包含指针,将对象分为noscan和scan两类,其中noscan代表没有指针,而scan则代表有指针,需要GC进行扫描。mcache和span的对应关系如下图所示:

mchache在初始化时是没有任何span的,在使用过程中会动态的从central中获取并缓存下来,跟据使用情况,每种class的span个数也不相同。上图所示,class 0的span数比class1的要多,说明本线程中分配的小对象要多一些。

cache作为线程的私有资源为单个线程服务,而central则是全局资源,为多个线程服务,当某个线程内存不足时会向central申请,当某个线程释放内存时又会回收进central。src/runtime/mcentral.go:mcentral定义了central数据结构:

lock: 线程间互斥锁,防止多线程读写冲突

spanclass : 每个mcentral管理着一组有相同class的span列表

nonempty: 指还有内存可用的span列表

empty: 指没有内存可用的span列表

nmalloc: 指累计分配的对象个数线程从central获取span步骤如下:

将span归还步骤如下:

从mcentral数据结构可见,每个mcentral对象只管理特定的class规格的span。事实上每种class都会对应一个mcentral,这个mcentral的集合存放于mheap数据结构中。src/runtime/mheap.go:mheap定义了heap的数据结构:

lock: 互斥锁

spans: 指向spans区域,用于映射span和page的关系

bitmap:bitmap的起始地址

arena_start: arena区域首地址

arena_used: 当前arena已使用区域的最大地址

central: 每种class对应的两个mcentral

从数据结构可见,mheap管理着全部的内存,事实上Golang就是通过一个mheap类型的全局变量进行内存管理的。mheap内存管理示意图如下:

系统预分配的内存分为spans、bitmap、arean三个区域,通过mheap管理起来。接下来看内存分配过程。

针对待分配对象的大小不同有不同的分配逻辑:

(0, 16B) 且不包含指针的对象: Tiny分配

(0, 16B) 包含指针的对象:正常分配

[16B, 32KB] : 正常分配

(32KB, -) : 大对象分配其中Tiny分配和大对象分配都属于内存管理的优化范畴,这里暂时仅关注一般的分配方法。

以申请size为n的内存为例,分配步骤如下:

Golang内存分配是个相当复杂的过程,其中还掺杂了GC的处理,这里仅仅对其关键数据结构进行了说明,了解其原理而又不至于深陷实现细节。1、Golang程序启动时申请一大块内存并划分成spans、bitmap、arena区域

2、arena区域按页划分成一个个小块。

3、span管理一个或多个页。

4、mcentral管理多个span供线程申请使用

5、mcache作为线程私有资源,资源来源于mcentral。

变量的基本类型与零值-GOLANG

golang - @amazeUI - 2017-06-27 18:04:36

go程序规范,首行命名这个文件属于哪个包,往下再导入其他包,定义常量,定义全局变量,定义结构,定义接口,方法体。

基本类型:

布尔型,bool,只有两个值,true和false,长度为一个字节,和php不同可以用0和1来代表。

整形,int/uint,根据运行平台的不同可能为32位int或者64位int。

8位整形(1个字节长度),int8/uint8,取值范围在2的8次方。还有16(长度为2个字节),32(4),64()8。其中int32位的别名为rune

字节型,byte,就是uint8的别名。

浮点型,float32/float64。长度为4/8字节。

复数类型,complex64/complex128,字节长度为8/16,这个暂时不清楚概念,还有一个保存指针用的类型uintptr,根据运行平台来确定是32还是64的。

其他值类型,array,struct,string。

引用类型,slice,map,chan

接口类型,interface

函数类型,func

以上是golang中的基本类型,有很多类型只了解了基本概念,还不知道怎么在实际编码中怎么去使用在什么场景中使用。

类型的零值,当一个类型声明了但是并没有赋值,会有一个相对应的默认值给这个类型。

对于包的引入,一定要用双引号,变量的字符串声明也要用双引号包起来,不然程序运行会报错。

var声明变量,var 变量名 类型=值或函数,变量名真的可以是中文,再也不怕词穷不知道起变量名了,当然,能用英文是最好的了。在声明变量的同时也可以不用声明类型,系统会自动推断,在能够确定类型的情况最好还是写上去,也可以不用var关键字只要在等号左边加上一个:冒号就可以了,但我也不喜欢这样做,我希望一切都是显式的。

变量组,在全局可以使用var()括号里面写变量,在方法体里面需要使用并行变量声明,例如var a,b,c int =1,2,3。

空白符,var a,_,c int = 1,2,3,在这里2这个值就被忽略了。

还有一种赋值方法 a:=1,这种赋值方法省略了var关键字与类型,这种方法在以后说是会用很大的用处。

类型转换,go当中的所有类型转换必须是显式转换,同类型转换,比如int可以和浮点互换,var a =1.1 b:= int(a),将ab打印出来是1.1和1。还可以将int转为string,打印出来就不一样了。下一博文写为什么不一样和常量运算符

话说这种格式还蛮好看的,从编辑器里面复制出来的

golang内存对齐

`

`

计算机结构中可能会要求内存地址进行对齐;也就是说,一个变量的地址是一个因子的倍数。例如

`

`

在golang上,开发者有义务使64位字长的数据原子访问是64位(8字节)对齐的。 在 全局变量,结构体和切片的第一个字长数据可以被认为是64位对齐,如果是嵌套的结构体8字节对齐,那么被嵌套的结构要是8字节对齐,并且放在结构体中的第一个

如果保证呢:

变量或开辟的结构体、数组和切片值中的第一个64位字可以被认为是8字节对齐的。

开辟的意思就是好通过声明,make,new方式创建的。就是说这样创建的64为字可以保证是64为对齐的

Golang 公共变量包——expvar

expvar 包是 Golang 官方提供的公共变量包,它可以辅助调试全局变量。支持一些常见的类型: float64 、 int64 、 Map 、 String 。如果我们的程序要用到上面提的四种类型(其中,Map 类型要求 Key 是字符串)。可以考虑使用这个包。

这些基础的功能就不多说了,大家可以直接看官方的 文档 。

看源码的时候发现一个非常有意思的调试接口, /debug/vars 会把所有注册的变量打印到接口里面。这个接口很有情怀。

感觉这个包还是针对简单变量,比如整形、字符串这种比较好用。

看到就写了,并没有什么沉淀,写得挺乱的。这个包很简单,但是里面还是有些可以借鉴的编码和设计。新版本的 Golang 已经能解析整形为 Key 的哈希表了,这个包啥时候能跟上支持一下?

golang 包中的全局变量 在引用包的文件中是同一个值吗

在C语言中引用外部变量,一般使用extern关键字。举个例子如下:

//1.c 中定义一个变量g_a

int g_a = 100;

golang全局变量 golang全局变量堆栈golang全局变量 golang全局变量堆栈


//2.c 中定义一个函数输出g_a

#include

extern int g_a; //这里需要用extern说明g_a是一个外部全局变量

void f() {

printf("%d\n", g_a);

}

//3.c main函数中调用f()

void f(); //声明函数f()

int main()

{

f();

}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, website.service08@gmail.com 举报,一经查实,本站将立刻删除。

联系我们

工作日:9:30-18:30,节假日休息