首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >golang GC分析?runtime.mallocgc似乎是首屈一指的解决方案;那么转向sync.Pool是解决方案吗?

golang GC分析?runtime.mallocgc似乎是首屈一指的解决方案;那么转向sync.Pool是解决方案吗?
EN

Stack Overflow用户
提问于 2017-03-30 13:46:15
回答 3查看 4.6K关注 0票数 4

我有一个用Go写的应用程序正在做消息处理,需要以20K/秒(可能更高)的速率从网络( UDP )提取消息,并且每个消息可以达到UDP数据包的最大长度(64KB-headersize),程序需要解码这个传入的数据包并编码成另一种格式并发送到另一个网络;

现在在CPURAM机上它运行良好,但偶尔会丢失一些数据包,编程模式已经遵循pipelines使用多个go-例程/通道,它占用了整个机器cpu负载的10%;因此它有可能使用更多的CPU%或内存来处理所有20K/s消息而不会丢失任何一个;然后我开始分析,根据我在cpu profile中找到的profilingruntime.mallocgc出现在顶部,也就是垃圾收集器运行时,我怀疑这个GC可能是它挂起几毫秒(或一些微秒)并丢失一些数据包的罪魁祸首,一些最佳实践表明切换到sync.Pool可能会有所帮助,但我切换到池似乎会导致更多的CPU争用和丢失更多的数据包,并且更频繁。

代码语言:javascript
复制
(pprof) top20 -cum (sync|runtime)
245.99s of 458.81s total (53.61%)
Dropped 487 nodes (cum <= 22.94s)
Showing top 20 nodes out of 22 (cum >= 30.46s)
      flat  flat%   sum%        cum   cum%
         0     0%     0%    440.88s 96.09%  runtime.goexit
     1.91s  0.42%  1.75%    244.87s 53.37%  sync.(*Pool).Get
    64.42s 14.04% 15.79%    221.57s 48.29%  sync.(*Pool).getSlow
    94.29s 20.55% 36.56%    125.53s 27.36%  sync.(*Mutex).Lock
     1.62s  0.35% 36.91%     72.85s 15.88%  runtime.systemstack
    22.43s  4.89% 41.80%     60.81s 13.25%  runtime.mallocgc
    22.88s  4.99% 46.79%     51.75s 11.28%  runtime.scanobject
     1.78s  0.39% 47.17%     49.15s 10.71%  runtime.newobject
    26.72s  5.82% 53.00%     39.09s  8.52%  sync.(*Mutex).Unlock
     0.76s  0.17% 53.16%     33.74s  7.35%  runtime.gcDrain
         0     0% 53.16%     33.70s  7.35%  runtime.gcBgMarkWorker
         0     0% 53.16%     33.69s  7.34%  runtime.gcBgMarkWorker.func2

池的使用是标准的

代码语言:javascript
复制
// create this one globally at program init
var rfpool = &sync.Pool{New: func() interface{} { return new(aPrivateStruct); }}

// get
rf := rfpool.Get().(*aPrivateStruct)

// put after done processing this message
rfpool.Put(rf)

不确定我做错了吗?或者想知道还有什么其他方法可以调优GC以使用更少的CPU%?go版本是1.8

该列表显示了pool.getSlow src to pool.go at golang.org中发生的大量锁争用

代码语言:javascript
复制
(pprof) list sync.*.getSlow
Total: 7.65mins
ROUTINE ======================== sync.(*Pool).getSlow in /opt/go1.8/src/sync/pool.go
  1.07mins   3.69mins (flat, cum) 48.29% of Total
         .          .    144:       x = p.New()
         .          .    145:   }
         .          .    146:   return x
         .          .    147:}
         .          .    148:
      80ms       80ms    149:func (p *Pool) getSlow() (x interface{}) {
         .          .    150:   // See the comment in pin regarding ordering of the loads.
      30ms       30ms    151:   size := atomic.LoadUintptr(&p.localSize) // load-acquire
     180ms      180ms    152:   local := p.local                         // load-consume
         .          .    153:   // Try to steal one element from other procs.
      30ms      130ms    154:   pid := runtime_procPin()
      20ms       20ms    155:   runtime_procUnpin()
     730ms      730ms    156:   for i := 0; i < int(size); i++ {
    51.55s     51.55s    157:       l := indexLocal(local, (pid+i+1)%int(size))
     580ms   2.01mins    158:       l.Lock()
    10.65s     10.65s    159:       last := len(l.shared) - 1
      40ms       40ms    160:       if last >= 0 {
         .          .    161:           x = l.shared[last]
         .          .    162:           l.shared = l.shared[:last]
         .       10ms    163:           l.Unlock()
         .          .    164:           break
         .          .    165:       }
     490ms     37.59s    166:       l.Unlock()
         .          .    167:   }
      40ms       40ms    168:   return x
         .          .    169:}
         .          .    170:
         .          .    171:// pin pins the current goroutine to P, disables preemption and returns poolLocal pool for the P.
         .          .    172:// Caller must call runtime_procUnpin() when done with the pool.
         .          .    173:func (p *Pool) pin() *poolLocal {
EN

回答 3

Stack Overflow用户

发布于 2017-05-04 04:13:13

sync.Pool在高并发负载的情况下运行缓慢。尝试在启动过程中分配所有结构一次,并多次使用。例如,您可以在启动时创建多个goroutine (工作程序),而不是对每个请求运行new goroutine。我推荐阅读这篇文章:https://software.intel.com/en-us/blogs/2014/05/10/debugging-performance-issues-in-go-programs

票数 2
EN

Stack Overflow用户

发布于 2017-03-30 15:49:13

https://golang.org/pkg/sync/#Pool

作为短期对象的一部分维护的空闲列表不适合使用池,因为在这种情况下开销不能很好地摊销。让这样的对象实现它们自己的空闲列表更有效

  1. 您可以尝试将GOGC值设置为大于100。

https://dave.cheney.net/2015/11/29/a-whirlwind-tour-of-gos-runtime-environment-variables

  1. 或,实现您自己的免费列表。

http://golang-jp.org/doc/effective_go.html#leaky_buffer

票数 1
EN

Stack Overflow用户

发布于 2019-04-06 07:04:30

Go 1.13 (Q4 2019)可能会改变这一点:请参阅CL 166961

最初的问题是issue 22950:“同步:避免清除每个GC上的已满池”

,我发现每个周期都有大约1000个分配,这让我很惊讶。这似乎表明Pool在每次GC时都会清除其全部内容。

一个peek at the implementation似乎表明了这一点。

结果:

sync:通过牺牲缓存平滑GC上的池行为

目前,每个池在每次GC开始时被完全清除。

对于大量使用Pool的用户来说,这是一个问题,因为它会在清除池之后立即导致分配高峰,从而影响吞吐量和延迟。

这个CL通过引入牺牲性缓存机制解决了这个问题。不清除池,而是删除牺牲性缓存,并将主缓存移到牺牲性缓存。因此,在稳定状态下,(大致)没有新的分配,但如果池使用率下降,对象仍将在两个GC内收集(而不是一个GC)。

这种牺牲性缓存方法还改善了Pool对GC动态的影响。

当前的方法导致池中的所有对象都是短暂的。但是,如果应用程序处于稳定状态,并且只是要重新填充其池,那么这些对象会影响实时堆大小,就好像它们是长期存在的一样。

由于在计算GC触发器和目标时,池化对象被视为短期对象,但在活动堆中充当长期对象,这会导致GC过于频繁地触发。

如果池化对象是应用程序堆的重要部分,这会增加GC的CPU开销。牺牲品缓存让池化对象作为长期对象影响GC触发器和目标。

这对Get/Put性能没有影响,但对substantially reduces the impact to the Pool user when a GC happens有影响。

PoolExpensiveNew通过大幅降低"New“函数的调用速度证明了这一点。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43109483

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档