Revision v0.01
Strings, slices 和 time.Time
都含有指针。所以以下场景需要小心:
time.Time
[]byte
string 的值是不可变的,[]byte
是可变的。string 可读性强,但是大多数 IO 是使用 []byte
来完成的。
[]byte
和 string 之间的转换。尽可能避免 []byte
和 string 之间的转换
另外: 使用 string 作为映射键是很常见的,但我们通常有的是一个 []byte
.
编译器针对这种情况实现了特定的优化:
var m map[string]string |
但不会对下列代码进行优化:
key := string(bytes) |
version 1:
s := request.ID |
version 2:
var b bytes.Buffer |
version 3:
r = fmt.Sprintf("%s %v %v", request.ID, client.Addr(), time.Now()) |
version 4:
b := make([]byte, 0, 40) |
version 5:
var b strings.Builder |
bench test 发现 version 4 最快最省,version 1 & 5一般,version 2 & 3 最差。
append
很方便,但很浪费。
func main() { |
会复制大量数据并创建大量垃圾。
如果知道事先知道 slice 的长度可以预先分配 slice ,并使用 index 赋值, 以避免复制。
方法一(bad): 始终分配缓冲区,从而给GC带来压力。
func (r *Reader) Read() ([]byte, error) |
方法二(good): 接收一个 []byte
缓冲区,填充给定的缓冲区,并返回读取的字节数。
func (r *Reader) Read(buf []byte) (int, error) |
*sync.Pool
sync.Pool
类型可以用来重用公共对象, 避免new,减少内存分配,降低GC压力。没有固定大小或最大容量。(注意:里面的值可能随时被回收。)
var pool = sync.Pool{ |
GOGC
goroutine 创建成本低廉,Go 语言设计时就考虑到了成千上万个 goroutine 的情况。
但是,每个 goroutine 确实会消耗 goroutine 堆栈的内存,目前至少为2k。就算什么也不做,1,000,000 个 goroutines 也要消耗 2GB 的内存。
如果不知道什么时候停止,就不要启动 goroutine,否则就是潜在的内存泄漏,因为 goroutine 会将其堆栈的内存以及可从堆栈访问的所有堆分配变量固定在堆栈上。
Go 处理网络IO时会使用高效的操作系统轮询机制(kqueue, epoll, windows IOCP等)。一个单一的操作系统线程将为许多等待的 goroutine 提供服务。但对于本地文件 IO,Go 不会实现任何 IO 轮询。 *os.File
上的每个操作在进行中都会消耗一个操作系统线程。大量使用本地文件 IO 可能会导致程序产生数百或数千个线程,对磁盘系统 产生成百上千的并发 IO 请求, 可能超出操作系统所允许的范围。所以需要用 pool of worker goroutines 或 buffered channel 作为 semaphore。
var semaphore = make(chan struct{}, 10) |
写服务器代码要注意:
尽可能避免将数据读取到 []byte
中并传递,不然可能将兆字节(或更多)的数据读取到内存中。这给 GC 带来了巨大压力,并会增加应用程序的平均延迟。
应该用 io.Reader
和 io.Writer
来构造处理管道,以限制每个请求使用的内存量。
如果用了大量的 io.Copy
,请考虑实现 io.ReaderFrom
/io.WriterTo
。 这些接口效率更高,并且能避免将内存复制到临时缓冲区中。
如果不知道所需的最长时间就不要 IO。
用 SetDeadline
, SetReadDeadline
, SetWriteDeadline
对每个网络请求设置超时。
defer
的开销很大, 因为它要记录 defer
参数的 闭包。
defer mu.Unlock()
相当于:
defer func() { |
如果计算量很小, defer 就会变得很昂贵,经典的例子是
mutex 解锁 struct 变量 或 map 查找。在这种情况下,可以选择避免 defer
。(权衡 性能 vs 可读性和维护性)。
cgo 调用类似于阻塞 IO,会消耗线程。不要大量循环调用 C 代码。
其实应该尽量避免使用 cgo:
benchstat
, pprof
, Execution Tracer
分析瓶颈。
本文由以下文章总结而来: