Golang Go语言中 copy 的速度很慢,怎么办?有替代么?

写了一个程序,需要不停处理输入。由于输入的长度绝对不会超过 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](/user/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


Golang Go语言中 copy 的速度很慢,怎么办?有替代么?
17 回复

单线程 IO ,跟语言没关系吧

更多关于Golang Go语言中 copy 的速度很慢,怎么办?有替代么?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


20000000 76.6 ns/op 0 B/op 0 allocs/op



LZ 感觉你应该再换一台机器试试…

mem copy 慢,我猜栽在了 CPU 的 numa 上。
不妨试试利用 runtime 锁定协程所在的线程,或者用 gccgo 编译。

错了,锁定协程所在的核。手机码字,思绪都乱了


能更明确一点么……


震惊,看来如果实在没法修好这个问题,我可以先暂时忽略它。


好的,我先研究下这个,感谢。

BenchmarkCopy-4 10000000 193 ns/op 0 B/op 0 allocs/op
PASS
ok go_tests 2.206s

扔到另一个 goroutine 里 copy 哈哈哈

不然你嫌弃它慢也没有什么意啊

— 一本正经的瞎说。

你尝试用 uint64 类型试试?


是这样么?:
data := make([]byte, uint64(4096))
replace := make([]byte, uint64(4050))
但是并没有改善。


试了下在 Benchmark 的开始加入
runtime.LockOSThread()
看似没啥效果 :(

看起来跟计算机本身有关系。我决定暂时先把这个问题放一边好了,先把程序写出来然后再看是怎么回事。


data := make([]uint64, 4096 / 8)
replace := make([]uint64, 4050 / 8 + 1)

这个意思……


棒极了!
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 ,已槽懵)。


如果我没理解错它的那个 memmove 的话…… 你在 copy 的时候应该可以转成 byte 来用…… 只是创建的时候用 uint64 也是可疑的……
主要是因为内存对齐…… 在没有内存重叠,且满足 8bytes 对齐的情况下(也就是可以一次装入寄存器中), memmove 每次会移动一整个 uint64 ,直到剩下一点尾巴,再进行细微地处理,而不对齐的情况下则是一个 byte 一个 byte 地复制……

如果按照 256 ns/op 的速度…

4096 * (1,000,000,000 / 256 ) = 16G/s
这个速度不算慢吧?

目测内存对齐的锅吧……


故事是这样的:楼主原先写了个 1 allocs/op, 16 B/op 的函数。

缺点你也看到了,一个 1 allocs ,同时需要建立很多的[]byte{}来 append ,之后 mallocgc 耗时会比较高。

然后那个函数的执行速度是 190 ns/op 。然后热爱性能的楼主决定优化一下那个函数,让它更快。这个帖子发生在优化后……


仍然在消化这些知识。先感谢。

FYI ,
https://groups.google.com/forum/#!topic/golang-nuts/-sAqYxebcUI

另外,你可以把每次 bench 的 cpuinfo 输出,对比 runtime.memmove 占比的变化,就能得出是否是对齐的问题。

在Golang中,copy函数用于复制切片(slice)的内容。确实,在某些情况下,copy操作可能会显得比较慢,这通常是因为它需要遍历源切片并将每个元素复制到目标切片中。然而,copy是Go语言标准库提供的最基本、最直接的方式来进行切片复制,因此在大多数情况下,它是符合预期的。

如果你发现copy操作成为了性能瓶颈,可以考虑以下几种优化策略:

  1. 减少复制次数:尽量减少不必要的切片复制操作,通过设计算法和数据结构来避免频繁的数据移动。

  2. 使用指针或引用:如果可能,考虑使用指针或引用来传递数据,而不是复制整个切片。这样可以在不牺牲安全性的前提下,提高性能。

  3. 并行处理:如果复制操作可以并行化,利用Go的并发特性来加速处理过程。

  4. 优化数据结构:有时,通过优化数据结构(如使用更高效的数据存储方式或索引)可以减少复制的需求。

至于替代方案,copy函数在Go语言中没有直接的替代者。它是切片复制的标准方法,且经过优化,通常能提供合理的性能。如果你确实需要更高效的复制机制,可能需要考虑使用更底层的语言特性(如C/C++的memcpy)或设计专门的数据处理流程,但这通常会增加代码的复杂性和维护成本。在大多数情况下,优化算法和数据结构,以及合理使用并发,是提升性能的更有效方法。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!