写了一个程序,需要不停处理输入。由于输入的长度绝对不会超过 N 且这段数据不需要考虑并发,聪明伶俐的楼主为了复用 Buffer ,决定用make([]byte, N)
申请一段大[]byte
,然后修改其中的内容。
然后,为了一次能一次修改大段内容,用到了copy
。但是测试一下,发现copy
在从src
复制大段数据的时候,速度真太慢了。代码:
package main
import (
"testing"
)
func BenchmarkCopy(b *testing.B) {
data := make([]byte, 4096)
replace := make([]byte, 4050)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
copy(data[2:], replace)
}
}
在我的机器上测试的结果:
#Go 1.7
[rain@localhost golang_copy_test]$ go test -bench . -cpuprofile cpu.out
testing: warning: no tests to run
BenchmarkCopy-2 1000000 1990 ns/op 0 B/op 0 allocs/op
PASS
ok _/home/rain/Develpment/Meta/golang_copy_test 2.016s
复制一段数据需要 1990 纳秒简直握草。 pprof 的结果显示时间大都消耗在了runtime.memmove
上。
换了台机器,结果是这样:
# Go 1.6
BenchmarkCopy-8 5000000 256 ns/op 0 B/op 0 allocs/op
ok _/home/ubuntu/workspace/go_tests/copy_test 1.552s
但, 256 纳秒也不是很快啊。
况且,累积效应之后,在楼主真正的代码里,那速度啪啪噗的看起来是这样:
BenchmarkWriter-2 1000000 12745 ns/op 0 B/op 0 allocs/op
PASS
(就是它的错,箭头 men 坚决的说到)
当然,考虑到楼主是个渣的实际情况,或许是楼主把事情搞错了,于是来求教下解决办法。
如果真的实在没有办法让copy
变快,那么有没有其他办法可以让楼主欢快的直接修改buffer
里的大段数据呢?这个需求表述起来应该就像:
buffer[i:i+1024] = newInput[:1024]
// 那么楼主,为什么你不用for
呢:因为更慢啊亲
// 那么楼主,你可以建个 0 Len , N Cap 的 Buffer 来append
啊:但是这样也没快多少啊而且之后还需要 reset
看来是内存对齐的锅。根据 @yangff 的提示做了一些测试,测试代码改成了这样:
func BenchmarkCopy(b *testing.B) {
data := make([]byte, 8192)
replace := make([]byte, 4096)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
copy(data[0:], replace)
}
}
所以我可以通过改 copy(data[0:], replace) 这一行改改变对齐,于是:
BenchmarkCopy-2 5000000 300 ns/op 0 B/op 0 allocs/op
BenchmarkCopy-2 500000 3218 ns/op 0 B/op 0 allocs/op
BenchmarkCopy-2 500000 2960 ns/op 0 B/op 0 allocs/op
BenchmarkCopy-2 500000 2831 ns/op 0 B/op 0 allocs/op
BenchmarkCopy-2 1000000 1398 ns/op 0 B/op 0 allocs/op
BenchmarkCopy-2 2000000 717 ns/op 0 B/op 0 allocs/op
BenchmarkCopy-2 3000000 432 ns/op 0 B/op 0 allocs/op
BenchmarkCopy-2 5000000 290 ns/op 0 B/op 0 allocs/op
BenchmarkCopy-2 5000000 291 ns/op 0 B/op 0 allocs/op
1
ooonme 2016-08-29 20:02:56 +08:00 via iPhone
单线程 IO ,跟语言没关系吧
|
2
zts1993 2016-08-29 20:20:04 +08:00 1
20000000 76.6 ns/op 0 B/op 0 allocs/op
LZ 感觉你应该再换一台机器试试... |
3
wweir 2016-08-29 20:23:23 +08:00 via Android 1
mem copy 慢,我猜栽在了 CPU 的 numa 上。
不妨试试利用 runtime 锁定协程所在的线程,或者用 gccgo 编译。 |
5
raincious OP |
6
pubby 2016-08-29 20:53:12 +08:00 1
BenchmarkCopy-4 10000000 193 ns/op 0 B/op 0 allocs/op
PASS ok go_tests 2.206s |
7
rrfeng 2016-08-29 21:12:28 +08:00
扔到另一个 goroutine 里 copy 哈哈哈
不然你嫌弃它慢也没有什么意啊 --- 一本正经的瞎说。 |
8
yangff 2016-08-29 21:19:10 +08:00 1
你尝试用 uint64 类型试试?
|
9
raincious OP |
10
yangff 2016-08-29 21:52:06 +08:00 1
|
11
raincious OP @yangff
棒极了! BenchmarkCopy-2 5000000 278 ns/op 0 B/op 0 allocs/op PASS ok _/home/rain/Develpment/Meta/golang_copy_test 1.682s 不过这就意味着如果我直接去用这样的方法,得手动每 8 个 byte 合并成一个 uint64 ,这也就不见得快了。 但,也奇怪啊,这两个数据尺寸是一样大的,为什么 copy 速度会不一样( runtime.memmove 的代码是 ASM ,已槽懵)。 |
12
yangff 2016-08-29 22:17:25 +08:00 1
@raincious
如果我没理解错它的那个 memmove 的话…… 你在 copy 的时候应该可以转成 byte 来用…… 只是创建的时候用 uint64 也是可疑的…… 主要是因为内存对齐…… 在没有内存重叠,且满足 8bytes 对齐的情况下(也就是可以一次装入寄存器中), memmove 每次会移动一整个 uint64 ,直到剩下一点尾巴,再进行细微地处理,而不对齐的情况下则是一个 byte 一个 byte 地复制…… |
13
chzyer 2016-08-29 22:21:16 +08:00 1
如果按照 256 ns/op 的速度...
4096 * (1,000,000,000 / 256 ) = 16G/s 这个速度不算慢吧? |
14
southwolf 2016-08-29 22:47:38 +08:00 1
目测内存对齐的锅吧……
|
15
raincious OP |
16
hooluupog 2016-08-30 00:38:15 +08:00 1
FYI ,
https://groups.google.com/forum/#!topic/golang-nuts/-sAqYxebcUI 另外,你可以把每次 bench 的 cpuinfo 输出,对比 runtime.memmove 占比的变化,就能得出是否是对齐的问题。 |