当您想基准一个处理io.Reader
的代码时,通常使用io.Pipe
与额外的io.Writer
进行流式传输数据。
看这个微不足道的示例基准。
func Benchmark_ioPipe(b *testing.B) {
r, w := io.Pipe()
i := 0
go func() {
for {
i++
if i == b.N {
_ = w.Close()
return
}
_, _ = w.Write([]byte("<this might be a dynamic piece of generated data>\n"))
}
}()
b.ReportAllocs()
// io.Copy acts as a dummy processor here, in real-world
// scenario you'd have an actual io.Reader consumer.
if _, err := io.Copy(io.Discard, r); err != nil {
b.Fatal(err.Error())
}
}
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Benchmark_ioPipe-12 1655488 729.8 ns/op 64 B/op 1 allocs/op
这种饲料的性能还不错,但也不是令人印象深刻的。牺牲了一些速度以在并发下实现数据安全,读取和写入都可以与管道中发生的同步并行调用。
让我们尝试提高针对io.Reader
的特定饲料的特定情况,不需要外部同步。
type pagesReader struct {
// next returns contents of the next page,
// io.EOF error indicates last page.
next func() ([]byte, error)
// buf keeps the data to be read.
buf []byte
}
func (r *pagesReader) Read(p []byte) (n int, err error) {
// Fill the reader buffer with pages
// until it exceeds the incoming buffer
// or reaches the end of pages.
for len(r.buf) < len(p) {
page, err := r.next()
r.buf = append(r.buf, page...)
if err != nil {
copy(p, r.buf)
return len(r.buf), err
}
}
// Put head of reader buffer in the incoming buffer.
copy(p, r.buf)
// Move remaining tail into the head of reader buffer.
remaining := r.buf[len(p):]
r.buf = r.buf[:len(remaining)]
copy(r.buf, remaining)
return len(p), nil
}
pagesReader
实现了io.Reader
,从用户定义的回调next func() ([]byte, error)
。
现在,让我们更新以前使用pagesReader
而不是io.Pipe
的基准。
func Benchmark_pagesReader(b *testing.B) {
i := 0
r := &pagesReader{
next: func() ([]byte, error) {
i++
if i == b.N {
return nil, io.EOF
}
return []byte("<this might be a dynamic piece of generated data>\n"), nil
},
}
b.ReportAllocs()
// io.Copy acts as a dummy processor here, in real-world
// scenario you'd have an actual io.Reader consumer.
if _, err := io.Copy(io.Discard, r); err != nil {
b.Fatal(err.Error())
}
}
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Benchmark_pagesReader-12 21766588 52.38 ns/op 64 B/op 1 allocs/op
非同步实现的速度要快得多(大约14倍),可能更适合基准,以降低对整体结果的影响。
当需要同步时,可以在next
函数本身中使用MUTEX对其进行管理。