译文 Go 高性能系列教程之六:一些建议和实践

yudotyang · 2021年06月11日 · 734 次阅读
本帖已被设为精华帖!

原文链接 https://dave.cheney.net/high-performance-go-workshop/gophercon-2019.html#tips-and-tricks)

这是一些随机的建议。也是该系列的最后一部分,包含一些微小的优化代码的技巧。

6.1 协程

Go 语言中最关键的特性就是协程

协程非常易用、创建成本低,你几乎可以认为是没有代价的。

Go 的运行时是为拥有成千上万个协程的程序而编写的,一个程序包含成千上万的协程并不奇怪。

但是,每个协程都需要一个最小的协程栈的内存空间,目前最小内存空间是 2kb。

2048 * 1,000,000 goroutines = 2GB 内存,而且他们还没有做任何事情。

6.1 要知道何时停止 goroutine

协程的的创建和运行的成本非常低。但就内存占用而言,他们的成本确实有限,但也不能创建无限个协程。

在你的程序中,每次你使用go关键词就能启动一个协程,但你必须要知道如何,以及何时该协程能结束。

在你的程序设计中,某些协程可能会运行直到程序结束才会退出。这些协程非常罕见,但也要遵循该规则)。

如果你不知道何时结束协程,那将会有潜在的内存泄漏的隐患。因为协程会将其内存以及从栈上可以访问的任何堆上分配的变量固定在堆内存上。

注意:永远不要开启一个不知道如何停止的协程

6.1.2 深入阅读

6.2 对于某些请求,Go 使用高效的网络轮询算法

Go 运行时使用有效的操作系统轮询机制(kqueue,epoll,windows IOCP 等)来处理网络 IO。 一个单一的操作系统线程将为许多等待的 goroutine 提供服务。

但是,对于本地文件 IO,Go 不会实现任何 IO 轮询。 * os.File 上的每个操作在进行中都会消耗一个操作系统线程。

大量使用本地文件 IO 可能导致您的程序产生数百或数千个线程。 可能超出您的操作系统所允许的范围。

您的磁盘子系统不希望能够处理成百上千的并发 IO 请求。

要限制并发阻塞 IO 的数量,请使用工作程序 goroutine 池或缓冲的通道作为信号灯。

var semaphore = make(chan struct{}, 10)

func processRequest(work *Work) {
      semaphore <- struct{}{} // acquire semaphore
      // process request
      <-semaphore // release semaphore
  }

6.3 当心应用程序中的 IO 因子

如果您正在编写服务器进程,则它的主要工作是多路复用通过网络连接的客户端和存储在应用程序中的数据。

大多数服务器程序都会接受请求,进行一些处理,然后返回结果。 这听起来很简单,但是根据结果,它可能会让客户端消耗服务器上大量(可能是无限制的)资源。 这里有一些注意事项:

  • 每个传入请求的 IO 请求数量; 单个客户端请求生成多少个 IO 事件? 它可能平均为 1,或者如果从缓存中提供了许多请求,则可能小于一个。
  • 服务查询所需的读取量; 它是固定的,N + 1 还是线性的(读取整个表以生成结果的最后一页)。

相对而言,如果内存很慢,那么 IO 太慢了,您应该不惜一切代价避免这样做。 最重要的是,避免在请求的上下文中进行 IO-不要让用户等待磁盘子系统写入磁盘甚至读取磁盘。

6.4 使用流式 IO 接口

尽可能避免将数据读入 [] byte 并将其传递。

根据请求,您可能最终将兆字节(或更多!)的数据读取到内存中。 这给 GC 带来了巨大压力,这将增加应用程序的平均延迟。

相反,可以使用 io.Reader 和 io.Writer 接口来构建流式处理以限制每个请求使用的内存量。

为了提高效率,如果您使用大量的 io.Copy,请考虑实现 io.ReaderFrom / io.WriterTo。 这些接口效率更高,并且避免将内存复制到临时缓冲区中。

6.6 超时,超时,超时

永远不要开启一个不知道耗费多少时间的 IO 操作。

你应该使用 SetDeadline,SetReadDeadline,SetWriteDeadline 函数给每一个网络请求设置超时机制。

6.6 Defer 函数代价很高,不是吗?

从历史上看,Defer 函数成本很高,因为它必须将 defer 函数的参数以闭包的形式存储。

defer mu.Unlock()

等价于

defer func() {
        mu.Unlock()
}()

如果完成的工作量很小,则 defer 会很昂贵,经典示例是将 struct 变量或映射查找周围的互斥锁解锁。

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
astaxie 将本帖设为了精华贴 06月12日 02:38
cczsunnyman GoCN 每日新闻 (2021-06-13) 中提及了此贴 06月14日 09:33
cloudy GoCN 每日新闻 (2021-06-14) 中提及了此贴 06月14日 15:07
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册