1. 背景
有的时候我们会遇到并发 IO 的情况,例如,并发爬虫下载网络上的图片。如果并发度过高或者下载的内容过大,会导致网络 IO 耗时急剧上升。这时候就需要优化一下每次网络IO 的耗时。
2. 网络下载图片用例
以下载网络数据为例,下面是典型的下载代码。
http.Getrsp.Body
3. ioutil.ReadAll
3.1. 源码分析
ioutil.ReadAllbytes.Buffer.ReadFrombufbytes.MinRead = 512
buf.ReadFrom(r)b.bufMinRead = 512b.buf
b.grow(n)bytes.Buffer
bytes.Buffer
b.grow(n)
b.bufb.bufcap(b.buf)b.buf = niln < 64b.buflen + nb.bufcap(b.buf)/2b.buf[b.offset:]b.bufb.offsetb.bufb.buflen + nb.bufcap(b.buf)/2makeSlicecap(b.buf)*2+ncopy(buf, b.buf[b.off:])
bytes.Buffer
readallbytes.Buffer
3.2. 资源分配分析
ioutil.ReadAlltest.data.rar

写一个下面的单测代码:
执行单元测试,并储存内存和cpu概要信息。
接下来使用 pprof 分析内存和cpu 的概要文件。
3.2.1. cpu 分析
首先分析 cpu 概要文件。在 bash 中输入:
TestReadAll

3.2.2. 内存分析
接下来是内存概要文件分析。在 bash 中输入:
ioutil.ReadAllioutil.ReadAllbytes.BufferGrow(n)
128MB 正好是测试文件大小向上取整的512字节的整数倍。

4. io.Copy
ioutil.ReadAll
那我们会想,可以直接避免内存频繁分配吗?反正内存也不会省,那我们在之前直接一次分配够了,之后就不会有额外的内存分配耗时了。
io.Copy
4.1. 预分配文件大小内存
io.Copy
io.Copybuf.ReadFromfile
执行单测生成 cpu 和内存概要文件:
ioutil.ReadAll

分析内存概要文件如下,可以发现的确有额外的内存分配,并且分配的内存是文件大小的两倍。这说明耗时还有进一步下降的空间。

4.2. 预分配双倍文件大小内存
在代码中预先分配双倍文件大小的内存:
执行单测,分析 cpu 和内存概要文件。
ioutil.ReadAll
内存概要分析如下,可以看到除了最开始的内存分配,代码内部没有额外的内存分配了,这也是耗时进一步下降的原因。

5. 并发压测
io.copyioutil.ReadAll
readAllDataiocpoyData
iocpoyData
5.1. cpu 分析
readAllDataiocopyData
runtime.makeSlicereadAllDatareadAllData

5.2. 内存分析
接下来看一下两者的内存分析
readAllDataiocopyDatareadAllDatabytes.makeSliceiocopyData

总结
io.Copy