文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Fasthttp 为什么比标准库快 10 倍 ?

2024-11-30 16:00

关注

上面是来自官方 Github 主页的项目介绍,抛开其介绍内容不谈,光从名字本身来看,作者对项目代码的自信程度可见一斑。

本文不会讲解 fasthttp​ 的应用方法,而是会重点分析 fasthttp 高性能的背后实现原理。

基准测试

我们可以通过基准测试看看 fasthttp​ 是否真的如描述所言,吊打标准库的 net/http,下面是官方提供的基准测试结果:

net/http

$ GOMAXPROCS=4 go test -bench='HTTPClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkNetHTTPClientDoFastServer-4 2000000 8774 ns/op 2619 B/op 35 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1TCP-4 500000 22951 ns/op 5047 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10TCP-4 1000000 19182 ns/op 5037 B/op 55 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100TCP-4 1000000 16535 ns/op 5031 B/op 55 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1Inmemory-4 1000000 14495 ns/op 5038 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10Inmemory-4 1000000 10237 ns/op 5034 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100Inmemory-4 1000000 10125 ns/op 5045 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1000Inmemory-4 1000000 11132 ns/op 5136 B/op 56 allocs/op

fasthttp

$ GOMAXPROCS=4 go test -bench='kClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkClientDoFastServer-4 50000000 397 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1TCP-4 2000000 7388 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10TCP-4 2000000 6689 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100TCP-4 3000000 4927 ns/op 1 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1Inmemory-4 10000000 1604 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10Inmemory-4 10000000 1458 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100Inmemory-4 10000000 1329 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1000Inmemory-4 10000000 1316 ns/op 5 B/op 0 allocs/op

基准结果对比

从基准测试结果来看,fasthttp​ 的执行速度要比标准库的 net/http​ 快很多,此外,fasthttp​ 的内存分配方面优化到了 0​, 完胜 net/http。

核心优化点

笔者选择的 valyala/fasthttp[1] 版本为 v1.45.0。

对象复用

workerPool

workerpool​ 对象表示 连接处理​ 工作池,这样可以控制连接建立后的处理方式,而不是像标准库 net/http​ 一样,对每个请求连接都启动一个 goroutine​ 处理, 内部的 ready​ 字段存储空闲的 workerChan​ 对象,workerChanPool​ 字段表示管理 workerChan 的对象池。

// workerpool.go
type workerPool struct {
ready []*workerChan

workerChanPool sync.Pool
}

type workerChan struct {
lastUseTime time.Time
ch chan net.Conn
}

请求/响应 对象

请求对象 Request​ 和响应对象 Response 都是通过对象池进行管理的,对应的代码如下:

// client.go

var (
requestPool sync.Pool
responsePool sync.Pool
)

// 从对象池中获取 Request 对象
func AcquireRequest() *Request {
...
}

// 归还 Request 对象到对象池中
func ReleaseRequest(req *Request) {
...
}

// 从对象池中获取 Response 对象
func AcquireResponse() *Response {
...
}

// 归还 Response 对象到对象池中
func ReleaseResponse(resp *Response) {
...
}

Cookie 对象

Cookie 对象也是通过对象池进行管理的,对应的代码如下:

// cookie.go

var cookiePool = &sync.Pool{
New: func() interface{} {
return &Cookie{}
},
}

// 从对象池中获取 Cookie 对象
func AcquireCookie() *Cookie {
...
}

// 归还 Cookie 对象到对象池中
func ReleaseCookie(c *Cookie) {
...
}

其他对象复用

$ grep -inr --include \*.go "sync.Pool" $(go list -f {{.Dir}} github.com/valyala/fasthttp) | wc -l

# 输出如下
38

通过输出结果可以看到,fasthttp​ 中一共有 38 个对象是通过对象池进行管理的,可以说几乎复用了所有对象,So Crazy!

[]byte 复用

fasthttp​ 中复用的对象在使用完成后归还到对象池之前,需要调用对应的 Reset​ 方法进行重置,如果对象中包含 []byte​ 类型的字段, 那么会直接进行复用,而不是初始化新的 []byte​, 例如 URI​ 对象的 Reset 方法:

// 重置 URI 对象
// 从方法的内部实现中可以看到,类型为 []byte 的所有字段都被复用了
func (u *URI) Reset() {
u.pathOriginal = u.pathOriginal[:0]
u.scheme = u.scheme[:0]
u.path = u.path[:0]
u.queryString = u.queryString[:0]
u.hash = u.hash[:0]
u.username = u.username[:0]
u.password = u.password[:0]

u.host = u.host[:0]
...
}

此外,涉及到单个字段的修改,如果字段是 []byte​ 类型,还是会直接复用,例如 Cookie 对象的这几个方法:

func (c *Cookie) SetValue(value string) {
c.value = append(c.value[:0], value...)
}

func (c *Cookie) SetValueBytes(value []byte) {
c.value = append(c.value[:0], value...)
}

func (c *Cookie) SetKey(key string) {
c.key = append(c.key[:0], key...)
}

func (c *Cookie) SetKeyBytes(key []byte) {
c.key = append(c.key[:0], key...)
}

上面几个方法的内部实现中,无一例外,都对 []byte 类型的参数进行了复用。

[]byte 和 string 转换

fasthttp​ 专门提供了 []byte​ 和 string​ 这两种常见的数据类型相互转换的方法 ,避免了 内存分配 + 复制,提升性能。

// s2b_new.go
func b2s(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

// b2s_new.go
func s2b(s string) (b []byte) {
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Cap = sh.Len
bh.Len = sh.Len
return b
}

高性能的 bytebufferpool

fasthttp​ 并没有直接使用标准库中的 bytes.Buffer​ 对象,而是引用了作者的另外一个包 valyala/bytebufferpool[2], 这个包的核心优化点是 避免内存拷贝 + 底层 byte 切片复用,感兴趣的读者可以看看官方给出的 基准测试结果[3]。

避免反射

fasthttp​ 中的所有 对象深拷贝​ 内部实现中都没有使用 反射​,而是手动实现的,这样可以完全规避 反射​ 带来的影响,例如 Cookie 对象的拷贝实现:

// cookie.go
// Cookie 对象拷贝实现
func (c *Cookie) CopyTo(src *Cookie) {
c.Reset()
c.key = append(c.key, src.key...)
c.value = append(c.value, src.value...)
c.expire = src.expire
c.maxAge = src.maxAge
c.domain = append(c.domain, src.domain...)
c.path = append(c.path, src.path...)
c.httpOnly = src.httpOnly
c.secure = src.secure
c.sameSite = src.sameSite
}

从上面的代码中可以看到,拷贝​ 的内部实现就是手动挨个复制字段,非常 原始 的解决方案。

另外,请求对象 Request​ 和响应对象 Response​ 的拷贝实现和 Cookie 有异曲同工之处:

// client.go
func (req *Request) CopyTo(dst *Request) {
...
}

func (resp *Response) CopyTo(dst *Response) {
...
}

fasthttp 的问题

软件工程没有银弹,高性能的背后必然是以某些条件作为代价的,fasthttp 的主要问题有:

多核系统的性能优化技巧

fasthttp 最佳实践

是否采用 fasthttp

fasthttp​ 是为一些高性能边缘场景设计的,如果你的业务需要支撑较高的 QPS​ 并且保持一致的低延迟时间,那么采用 fasthttp​ 是非常合理的, 反之 fasthttp​ 可能并不适合 (增加开发复杂度和开发者心智负担)。大多数情况下,标准库 net/http​ 是更好的选择,因为它简单易用并且兼容性很高。 如果你的业务流量很少,那么两者之间的 所谓性能差异 几乎可以忽略。

Reference

引用链接

[1]​ valyala/fasthttp: ​​https://github.com/valyala/fasthttp​

[2]​ valyala/bytebufferpool: ​​https://github.com/valyala/bytebufferpool​

[3]​ 基准测试结果: ​​https://omgnull.github.io/go-benchmark/buffer/​

[4]​ 这个链接: ​​https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/​

[5]​ 竞态检测: ​​https://go.dev/doc/articles/race_detector​

[6]​ valyala/fasthttp: ​​https://github.com/valyala/fasthttp​

[7]​ fasthttp 快在哪里: ​​https://xargin.com/why-fasthttp-is-fast-and-the-cost-of-it/​

[8]​ fasthttp剖析: https://www.jianshu.com/p/a0e766f8dcb0

来源:洋芋编程内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯