Slices instead of maps for (key -> value) entries
今天看了下fasthttp的源码,发现了一个有趣的地方,遂研究了一下。
详情请直接看原作者的一个slides
这里简单分析一下为何这么替换和替换的一些细节。
第一眼看下来,what,哪边复用,有什么精妙的,但是要结合场景考虑,这个是专门为net.http写的,每一次请求它是不同的。
fasthttp中的reset思路是
sm=sm[:0]
不是我们一般习惯的
sm=nil
由于切片的源码是
// runtime/slice.go type slice struct { array unsafe.Pointer // 数组指针 len int // 长度 cap int // 容量 }
可见,这种思路完全是复用底层内存的,但是仅仅如此吗?作者还有个很精妙的地方
就是扩容,这种扩容是持久的,上一个请求的sliceMap够用,这个不够用了,ok,扩容,下一个如果和上一个相同的需求,不需要再次扩容了,一个服务的异样请求统共不会太多,所以一段时间后就基本不会发生扩容了。
这里的kv.key和kv.value,在前面的基础上就使用的是append(s[:0], k...)这种思路,你这里可能会问这不是发生string和slice的转换吗,如果看汇编的话,这种操作是会进行编译优化的。
没有call stringtoslice,性能是可以保证的。
源码示例
type argsKV struct { key []byte value []byte noValue bool } // 增加新的kv func appendArg(args []argsKV, key, value string, noValue bool) []argsKV { var kv *argsKV args, kv = allocArg(args) // 复用原来key的内存空间 kv.key = append(kv.key[:0], key...) if noValue { kv.value = kv.value[:0] } else { // 复用原来value的内存空间 kv.value = append(kv.value[:0], value...) } kv.noValue = noValue return args } func allocArg(h []argsKV) ([]argsKV, *argsKV) { n := len(h) if cap(h) > n { // 复用底层数组空间,不用分配 h = h[:n+1] } else { // 空间不足再分配 h = append(h, argsKV{}) } return h, &h[n] }
至于别的区别,可以看slice和map的源码,首先两者的struct就能看出这种方式的优点。
不过,话转回来,这好像让编程变得更麻烦了点,因为golang的设计就是不推荐走手动管内存的。如果没有性能瓶颈,还是建议采用Golang核心队伍的的思路。
一个没有高级趣味的人。
email:hushui502@gmail.com