设为首页 加入收藏

TOP

高性能go服务之高效内存分配(三)
2019-09-03 03:39:54 】 浏览:129
Tags:高性能 服务 高效 内存 分配
有用 --- 它可以让我们写出更加灵活的代码。程序里常用的热路径代码的相关实例就是标准库提供的hash包。hash包定义了一系列常规接口并提供了几个具体实现。我们看一个例子。

package main

import (
        "fmt" "hash/fnv" ) func hashIt(in string) uint64 { h := fnv.New64a() h.Write([]byte(in)) out := h.Sum64() return out } func main() { s := "hello" fmt.Printf("The FNV64a hash of '%v' is '%v'\n", s, hashIt(s)) } 

构建检查逃逸分析结果:

./foo1.go:9:17: inlining call to fnv.New64a
./foo1.go:10:16: ([]byte)(in) escapes to heap ./foo1.go:9:17: hash.Hash64(&fnv.s·2) escapes to heap ./foo1.go:9:17: &fnv.s·2 escapes to heap ./foo1.go:9:17: moved to heap: fnv.s·2 ./foo1.go:8:24: hashIt in does not escape ./foo1.go:17:13: s escapes to heap ./foo1.go:17:59: hashIt(s) escapes to heap ./foo1.go:17:12: main ... argument does not escape 

也就是说,hash对象,输入字符串,以及代表输入的[]byte全都会逃逸到堆上。我们肉眼看上去显然不会逃逸,但是接口类型限制了编译器。不通过hash包的接口就没有办法安全地使用具体的实现。 那么效率相关的开发人员应该做些什么呢?

我们在构建Centrifuge的时候遇到了这个问题,Centrifuge在热代码路径对小字符串进行非加密hash。因此我们建立了fasthash库。构建它很直接,困难工作依旧在标准库里做。fasthash只是在没有使用堆分配的情况下重新打包了标准库。

直接来看一下fasthash版本的代码

package main

import (
        "fmt" "github.com/segmentio/fasthash/fnv1a" ) func hashIt(in string) uint64 { out := fnv1a.HashString64(in) return out } func main() { s := "hello" fmt.Printf("The FNV64a hash of '%v' is '%v'\n", s, hashIt(s)) } 

看一下逃逸分析输出

./foo2.go:9:24: hashIt in does not escape ./foo2.go:16:13: s escapes to heap ./foo2.go:16:59: hashIt(s) escapes to heap ./foo2.go:16:12: main ... argument does not escape 

唯一产生的逃逸就是因为fmt.Printf()方法的动态特性。尽管通常我们更喜欢是用标准库,但是在一些情况下需要进行权衡是否要提高分配效率。

一个小窍门

我们最后这个事情,不够实际但是很有趣。它有助我们理解编译器的逃逸分析机制。 在查看所涵盖优化的标准库时,我们遇到了一段相当奇怪的代码。

// noescape hides a pointer from escape analysis.  noescape is
// the identity function but escape analysis doesn't think the // output depends on the input. noescape is inlined and currently // compiles down to zero instructions. // USE CAREFULLY! //go:nosplit func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } 

这个方法会让传递的指针逃过编译器的逃逸分析检查。那么这意味着什么呢?我们来设置个实验看一下。

package main

import (
        "unsafe" ) type Foo struct { S *string } func (f *Foo) String() string { return *f.S } type FooTrick struct { S unsafe.Pointer } func (f *FooTrick) String() string { return *(*string)(f.S) } func NewFoo(s string) Foo { return Foo{S: &s} } func NewFooTrick(s string) FooTrick { return FooTrick{S: noescape(unsafe.Pointer(&s))} } func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } func main() { s := "hello" f1 := NewFoo(s) f2 := NewFooTrick(s) s1 := f1.String() s2 := f2.String() } 

这个代码包含两个相同任务的实现:它们包含一个字符串,并使用String()方法返回所持有的字符串。但是,编译器的逃逸分析说明FooTrick版本根本没有逃逸。

./foo3.go:24:16: &s escapes to heap
./foo3.go:23:23: moved to heap: s
./foo3.go:27:28: NewFooTrick s does not escape
./foo3.go:28:45: NewFooTrick &s does not escape
./foo3.go:31:33: noescape p does not escape
./foo3.go:38:14: main &s does not escape
./foo3.go:39:19: main &s does not escape
./foo3.go:40:17: main f1 does not escape
./foo3.go:41:17: main f2 does not escape

这两行是最相关的

./foo3.go:24:16: &s escapes to heap
./foo3.go:23:23: moved to heap: s

这是编译器认为NewFoo()``方法把拿了一个string类型的引用并把它存到了结构体里,导致了逃逸。但是NewFooTrick()方法并没有这样的输出。如果去掉noescape(),逃逸分析会把FooTrick结构体

首页 上一页 1 2 3 4 下一页 尾页 3/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇prisma反向代理 下一篇GO指南练习:切片

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目