美文网首页程序员go学习
Go语言·Binary Tree算法之华山论剑

Go语言·Binary Tree算法之华山论剑

作者: Sunface撩技术 | 来源:发表于2017-10-23 12:59 被阅读110次

    迁移自CSDN:http://blog.csdn.net/erlib/article/details/53956732

    前言

    在benchmarkgame(世界上最火的性能对比网站)上,Go语言一直有一个槽点,就是极其慢的binary tree性能,执行用时40秒 (我的机器上,16秒),与此对比,Java版本是6秒,那么问题来了:为什么慢得令人发指?我们来深入研究下慢的原因,然后看看能否对其进行改进。

    对于binary tree算法中,最耗性能的地方就是海量的node分配和bottomUpTree()递归函数的调用,与这两项对应的go的特性就是gc的goroutine的堆栈分配。

    GC

    这个世界没有完美的GC,任何选择都有代价,一般来说就是低延迟和高吞吐的权衡。


    问题描述

    Java采用的是分代GC,优点是node的分配非常轻量,缺点就是分代gc需要使用更多的内存空间,同时当对象被移动到tenured堆时,会发生大量的内存拷贝。 Go的gc不是分代的,因此在node的分配上需要消耗更多的资源。Go的gc选择了超低的延迟,同时牺牲了部分吞吐,对于绝大多数应用来说,Go的选择是非常正确的,但是对于binary tree这种算法来说,就不是很适合了。

    解决方案

    针对GC的优化,有两个通用的解决方案就是提前分配合适的堆栈空间和对象复用。对于binary tree算法,就是Nodes的预先分配和复用。

    Goroutine的堆栈

    轻量的Goroutine是Go语言的灵魂所在


    问题描述

    为了让goroutine尽可能轻量,go仅仅为每个goroutine分配了2KB的初始堆栈大小,在之后Go会根据需要动态的扩展堆栈大小。同样,对于绝大多数场景,这种选择都是非常正确的,但是针对binary tree算法,这种选择就有了一些问题。

    Go是在每次函数调用之前检查goroutine的堆栈大小,如果发现当前堆栈不够用,就会重新分配一个新的堆栈空间,然后将旧的堆栈拷贝到新的里。这种操作开销是很小的,但是在binary tree中,bottomUpTree()基本上不做什么工作,调用却是极其频繁,这样一来再小的开销累积起来也会非常可观。而且这个函数的调用是深递归,当堆栈需要增长时,可能会拷贝几次,不仅仅是一次!解决方案 将bottomUpTree()改为非递归的函数,虽然不易实现,但是还是可以做到的。

    新旧Binary tree实现对比

    没有对比,就没有伤害!


    旧版本代码链接

    运行用时:

    
    > time go run old.go 20
    
    stretch tree of depth 21 check: -1
    
    2097152 trees of depth 4 check: -2097152
    
    524288 trees of depth 6 check: -524288
    
    131072 trees of depth 8 check: -131072
    
    32768 trees of depth 10 check: -32768
    
    8192 trees of depth 12 check: -8192
    
    2048 trees of depth 14 check: -2048
    
    512 trees of depth 16 check: -512
    
    128 trees of depth 18 check: -128
    
    32 trees of depth 20 check: -32
    
    long lived tree of depth 20 check: -1
    
    real0m16.279s
    
    user1m47.569s
    
    sys0m2.663s
    
    

    新版本代码链接

    运行用时

    time go run new.go 20
    stretch tree of depth 21 check: -1
    
    2097152 trees of depth 4 check: -2097152
    
    524288 trees of depth 6 check: -524288
    
    131072 trees of depth 8 check: -131072
    
    32768 trees of depth 10 check: -32768
    
    8192 trees of depth 12 check: -8192
    
    2048 trees of depth 14 check: -2048
    
    512 trees of depth 16 check: -512
    
    128 trees of depth 18 check: -128
    
    32 trees of depth 20 check: -32
    
    long lived tree of depth 20 check: -1
    
    dur: 1.71074946s
    
    
    
    real0m1.914s
    
    user0m10.149s
    
    sys0m0.157s
    

    结论

    性能从16.28秒提升到了1.91秒,提升巨大!

    这里提出的解决方案看似是针对binary tree,其实对于任何GC语言和使用场景来说都是通用的。

    牢记这两种解决方案吧:-内存空间预分配-对象复用

    如果您喜欢这篇文章,请点击喜欢;如果想及时获得最新的咨询,请点击关注。您的支持是对作者都是最大的激励,万分感激!By 孙飞

    相关文章

      网友评论

        本文标题:Go语言·Binary Tree算法之华山论剑

        本文链接:https://www.haomeiwen.com/subject/jetauxtx.html