重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
试图通过拷贝 *big.Int 指针所指的结构:
创新互联建站始终坚持【策划先行,效果至上】的经营理念,通过多达10余年累计超上千家客户的网站建设总结了一套系统有效的全网推广解决方案,现已广泛运用于各行各业的客户,其中包括:成都航空箱等企业,备受客户赞美。
这种方式是错误的,因为 big.Int 结构内部有 slice ,拷贝结构的话内部的 slice 仍然是共享内存。
点击运行测试
思想:
思想:
copier 内部实现使用了 reflect 。
思想
Benchmark测试
big.Int = 10
big.Int = 100000000222222222222222222220000000000000000000
比较两次运行的结果,发现:
+ 0 是最好的选择
不知道你有没有听过这么一句:在使用 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 语言中很多模块设计得太精妙了。
差不多说清楚了,能力有限,有不对的地方欢迎留言讨论,源码位置还是问的群里大佬 _
1,go的变量声明顺序是:”先写变量名,再写类型名“,此与C/C++的语法孰优孰劣,可见下文解释:
2,go是通过package来组织的(与python类似),只有package名为main的包可以包含main函数,一个可执行程序有且仅有一个main包,通过import关键字来导入其他非main包。
3,可见性规则。go语言中,使用大小写来决定该常量、变量、类型、接口、结构或函数是否可以被外部包含调用。根据约定,函数名首字母小写即为private,函数名首字母大写即为public。
4,go内置关键字(25个均为小写)。
5,函数不用先声明,即可使用。
6,在函数内部可以通过 := 隐士定义变量。(函数外必须显示使用var定义变量)
7,go程序使用UTF-8编码的纯Unicode文本编写。
8,使用big.Int的陷阱:
9,从技术层面讲,go语言的语句是以分号分隔的,但这些是由编译器自动添加的,不用手动输入,除非需要在同一行中写入多个语句。没有分号及只需少量的逗号和圆括号,使得go语言的程序更容易阅读。
10,go语言只有一个循环结构——for循环。
11,go里的自增运算符只有——“后++”
12,go语言中的slice用法类似python中数组,关于slice的详细用法可见:
13,函数也是一个值,使用匿名函数返回一个值。
14,函数闭包的使用,闭包是一个匿名函数值,会引用到其外部的变量。