Golang copy 的速度很慢,怎么办?有替代么? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
raincious
V2EX    Go 编程语言

Golang copy 的速度很慢,怎么办?有替代么?

  •  
  •   raincious 2016-08-29 19:37:55 +08:00 2840 次点击
    这是一个创建于 3336 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    第 1 条附言    2016-08-30 00:04:31 +08:00

    看来是内存对齐的锅。根据 @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) 这一行改改变对齐,于是:

    当 copy(data[0:], replace):

    BenchmarkCopy-2 5000000 300 ns/op 0 B/op 0 allocs/op 

    当 copy(data[2:], replace):

    BenchmarkCopy-2 500000 3218 ns/op 0 B/op 0 allocs/op 

    当 copy(data[4:], replace):

    BenchmarkCopy-2 500000 2960 ns/op 0 B/op 0 allocs/op 

    当 copy(data[8:], replace):

    BenchmarkCopy-2 500000 2831 ns/op 0 B/op 0 allocs/op 

    当 copy(data[16:], replace):

    BenchmarkCopy-2 1000000 1398 ns/op 0 B/op 0 allocs/op 

    当 copy(data[32:], replace):

    BenchmarkCopy-2 2000000 717 ns/op 0 B/op 0 allocs/op 

    当 copy(data[64:], replace):

    BenchmarkCopy-2 3000000 432 ns/op 0 B/op 0 allocs/op 

    当 copy(data[128:], replace):

    BenchmarkCopy-2 5000000 290 ns/op 0 B/op 0 allocs/op 

    当 copy(data[256:], replace):

    BenchmarkCopy-2 5000000 291 ns/op 0 B/op 0 allocs/op 
    第 2 条附言    2016-08-30 12:41:49 +08:00
    16 条回复    2016-08-30 00:38:15 +08:00
    ooonme
        1
    ooonme  
       2016-08-29 20:02:56 +08:00 via iPhone
    单线程 IO ,跟语言没关系吧
    zts1993
        2
    zts1993  
       2016-08-29 20:20:04 +08:00   1
    20000000 76.6 ns/op 0 B/op 0 allocs/op



    LZ 感觉你应该再换一台机器试试...
    wweir
        3
    wweir  
       2016-08-29 20:23:23 +08:00 via Android   1
    mem copy 慢,我猜栽在了 CPU 的 numa 上。
    不妨试试利用 runtime 锁定协程所在的线程,或者用 gccgo 编译。
    wweir
        4
    wweir  
       2016-08-29 20:24:56 +08:00 via Android   1
    @wweir 错了,锁定协程所在的核。手机码字,思绪都乱了
    raincious
        5
    raincious  
    OP
       2016-08-29 20:29:07 +08:00
    @ooonme
    能更明确一点么……

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

    @wweir
    好的,我先研究下这个,感谢。
    pubby
        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
    rrfeng
        7
    rrfeng  
       2016-08-29 21:12:28 +08:00
    扔到另一个 goroutine 里 copy 哈哈哈

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

    --- 一本正经的瞎说。
    yangff
        8
    yangff  
       2016-08-29 21:19:10 +08:00   1
    你尝试用 uint64 类型试试?
    raincious
        9
    raincious  
    OP
       2016-08-29 21:35:14 +08:00
    @yangff
    是这样么?:
    data := make([]byte, uint64(4096))
    replace := make([]byte, uint64(4050))
    但是并没有改善。

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

    看起来跟计算机本身有关系。我决定暂时先把这个问题放一边好了,先把程序写出来然后再看是怎么回事。
    yangff
        10
    yangff  
       2016-08-29 21:52:06 +08:00   1
    @raincious
    data := make([]uint64, 4096 / 8)
    replace := make([]uint64, 4050 / 8 + 1)

    这个意思……
    raincious
        11
    raincious  
    OP
       2016-08-29 22:13:39 +08:00
    @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 ,已槽懵)。
    yangff
        12
    yangff  
       2016-08-29 22:17:25 +08:00   1
    @raincious
    如果我没理解错它的那个 memmove 的话…… 你在 copy 的时候应该可以转成 byte 来用…… 只是创建的时候用 uint64 也是可疑的……
    主要是因为内存对齐…… 在没有内存重叠,且满足 8bytes 对齐的情况下(也就是可以一次装入寄存器中), memmove 每次会移动一整个 uint64 ,直到剩下一点尾巴,再进行细微地处理,而不对齐的情况下则是一个 byte 一个 byte 地复制……
    chzyer
        13
    chzyer  
       2016-08-29 22:21:16 +08:00   1
    如果按照 256 ns/op 的速度...

    4096 * (1,000,000,000 / 256 ) = 16G/s
    这个速度不算慢吧?
    southwolf
        14
    southwolf  
       2016-08-29 22:47:38 +08:00   1
    目测内存对齐的锅吧……
    raincious
        15
    raincious  
    OP
       2016-08-29 23:18:06 +08:00
    @chzyer
    故事是这样的:楼主原先写了个 1 allocs/op, 16 B/op 的函数。

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

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

    @yangff
    仍然在消化这些知识。先感谢。
    hooluupog
        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 占比的变化,就能得出是否是对齐的问题。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2366 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 00:57 PVG 08:57 LAX 17:57 JFK 20:57
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86