189 8069 5689

go语言uintptr go语言适合做什么

Go 数据类型(三)整型及运算符

int8 , uint8

创新互联公司长期为近千家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为向阳企业提供专业的成都网站设计、做网站向阳网站改版等技术服务。拥有十余年丰富建站经验和众多成功案例,为您定制开发。

int16 , uint16

int32 , uint32

int64 , uint64

int , uint , uintptr

Go为强类型语言,所以上述类型默认为不同类型

如需运算,可通过强制类型转换

+ , - , * , / , %

, , == , = , = 和 !=

比较运算符计算的结果是布尔值

位与: xy

位或: x|y

异或: x^y

取反: ^x

左移:

右移:

逻辑运算符的计算结果也是布尔值

逻辑与: xy

逻辑或: x||y

逻辑非: !x

Go语言使用 map 时尽量不要在 big map 中保存指针

不知道你有没有听过这么一句:在使用 map 时尽量不要在 big map 中保存指针。好吧,你现在已经听过了:)为什么呢?原因在于 Go 语言的垃圾回收器会扫描标记 map 中的所有元素,GC 开销相当大,直接GG。

这两天在《Mastering Go》中看到 GC 这一章节里面对比 map 和 slice 在垃圾回收中的效率对比,书中只给出结论没有说明理由,这我是不能忍的,于是有了这篇学习笔记。扯那么多,Show Your Code

这是一个简单的测试程序,保存字符串的 map 和 保存整形的 map GC 的效率相差几十倍,是不是有同学会说明明保存的是 string 哪有指针?这个要说到 Go 语言中 string 的底层实现了,源码在 src/runtime/string.go里,可以看到 string 其实包含一个指向数据的指针和一个长度字段。注意这里的是否包含指针,包括底层的实现。

Go 语言的 GC 会递归遍历并标记所有可触达的对象,标记完成之后将所有没有引用的对象进行清理。扫描到指针就会往下接着寻找,一直到结束。

Go 语言中 map 是基于 数组和链表 的数据结构实现的,通过 优化的拉链法 解决哈希冲突,每个 bucket 可以保存 8 对键值,在 8 个键值对数据后面有一个 overflow 指针,因为桶中最多只能装 8 个键值对,如果有多余的键值对落到了当前桶,那么就需要再构建一个桶(称为溢出桶),通过 overflow 指针链接起来。

因为 overflow 指针的缘故,所以无论 map 保存的是什么,GC 的时候就会把所有的 bmap 扫描一遍,带来巨大的 GC 开销。官方 issues 就有关于这个问题的讨论, runtime: Large maps cause significant GC pauses #9477

无脑机翻如下:

如果我们有一个map [k] v,其中k和v都不包含指针,并且我们想提高扫描性能,则可以执行以下操作。

将“ allOverflow [] unsafe.Pointer”添加到 hmap 并将所有溢出存储桶存储在其中。 然后将 bmap 标记为noScan。 这将使扫描非常快,因为我们不会扫描任何用户数据。

实际上,它将有些复杂,因为我们需要从allOverflow中删除旧的溢出桶。 而且它还会增加 hmap 的大小,因此也可能需要重新整理数据。

最终官方在 hmap 中增加了 overflow 相关字段完成了上面的优化,这是具体的 commit 地址。

下面看下具体是如何实现的,源码基于 go1.15,src/cmd/compile/internal/gc/reflect.go 中

通过注释可以看出,如果 map 中保存的键值都不包含指针(通过 Haspointers 判断),就使用一个 uintptr 类型代替 bucket 的指针用于溢出桶 overflow 字段,uintptr 类型在 GO 语言中就是个大小可以保存得下指针的整数,不是指针,就相当于实现了 将 bmap 标记为 noScan, GC 的时候就不会遍历完整个 map 了。随着不断的学习,愈发感慨 GO 语言中很多模块设计得太精妙了。

差不多说清楚了,能力有限,有不对的地方欢迎留言讨论,源码位置还是问的群里大佬 _

Golang|切片原理

在Golang语言开发过程中,我们经常会用到数组和切片数据结构,数组是固定长度的,而切片是可以扩张的数组,那么切片底层到底有什么不同?接下来我们来详细分析一下内部实现。

首先我们来看一下数据结构

这里的array其实是指向切片管理的内存块首地址,而len就是切片的实际使用大小,cap就是切片的容量。

我们可以通过下面的代码输出slice:

这么分析下来,我们可以了解如下内容:

使用一个切片通常有两种方法:

另一种是slice = make([]int, len, cap)这种方法,称为分配内存。

创建一个slice,实质上是在分配内存。

这里跟一下细节,math.MulUintptr是基于底层的指针计算乘法的,这样计算不会导致超出int大小,这个方法在后面会经常用到。

同样,对于int64的长度,也有对应的方法

而实际分配内存的操作调用mallocgc这个分配内存的函数,这个函数以后再分析。

我们了解切片和数组最大的不同就是切片能够自动扩容,接下来看看切片是如何扩容的

这里可以看到,growslice是返回了一个新的slice,也就是说如果发生了扩容,会发生拷贝。

所以我们在使用过程中,如果预先知道容量,可以预先分配好容量再使用,能提高运行效率。

copy这个函数在内部实现为slicecopy

还有关于字符串的拷贝

这里显示了可以把string拷贝成[]byte,不能把[]byte拷贝成string。

1、切片的数据结构是 array内存地址,len长度,cap容量

2、make的时候需要注意 容量 * 长度 分配的内存大小要小于264,并且要小于可分配的内存量,同时长度不能大于容量。

3、内存增长的过程:

4、当发生内存扩容时,会发生拷贝数据的现象,影响程序运行的效率,如果可以,要先分配好指定的容量

5、关于拷贝,可以把string拷贝成[]byte,不能把[]byte拷贝成string。


本文名称:go语言uintptr go语言适合做什么
本文地址:http://cdxtjz.cn/article/ddpoohs.html

其他资讯