Slices instead of maps for (key -> value) entries

今天看了下fasthttp的源码,发现了一个有趣的地方,遂研究了一下。

 

 

详情请直接看原作者的一个slides

https://docs.google.com/presentation/d/e/2PACX-1vTxoBN41dYFB8aV8c0SDET3B2htsAavXPAwR-CMyfT2LfARR2KjOt8EPIU1zn8ceSuxrL8BmkOqqL_c/pub?start=false&loop=false&delayms=3000&slide=id.g524654fd95_0_111

 

 

这里简单分析一下为何这么替换和替换的一些细节。

 

第一眼看下来,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核心队伍的的思路。

posted @ 2020-05-08 11:58  zhangyu63  阅读(172)  评论(0编辑  收藏  举报