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

公众号