Golang中国

最近对一个支持高并发的服务做流量压测,发现测试条件相同的情况下(4000路直播流,码率一致),16核cpu测试结果是 cpu=1000% 左右,播放出现卡顿。8核cpu测试结果是 cpu=600% 左右,播放正常。小弟对这种情况表示很纳闷,硬件上升一个档次,性能应该会上升一个台阶才对,可结果确实让人不可接受。有在网上的人说核数多了,cpu上下文切换更频繁了,小弟也觉得有一点道理,但是goroutine调度器也不至于这么坑吧,核数多了,性能就直线下降?????请大牛指教!!!!!万分感谢。

14 回复
xyz
#1 xyz • 2016-11-24 18:45

觉得应该是两个方面:
1.业务上应该有用到锁,比如time.Now, math.Rand之类的,这些是用全局锁的,会导致cpu利用率下降
2.多核的调度系统线程上下文切换以及线程在核上出现漂移等,更多的协程调度也会消耗更多CPU

可以在16核上 启动两个实例用taskset分别绑定在8个核上,两个合在一起性能应该会上来,4个4核的性能应该更好一点。

stevewang
#2 stevewang • 2016-11-24 19:44

我觉得更有可能是你的程序有重大缺陷。

xyz
#3 xyz • 2016-11-24 20:26

应该不是,写个http的服务只输出hello world,压测一下,也是这样,cpu利用率随着核数的增加会下降。锁是不可避免的,fmt.Print低层应该也会用到锁

xgfone
#4 xgfone • 2016-11-25 11:16

测试:

  • CPUMem:2核4GB
  • OS: CentOS 7.2 终端界面
  • GO: Go 1.7.3

代码如下:

package main

import (
    "runtime"
)

var (
    LEN1 = 200000
    LEN2 = 1024
)

func f() {
    for {
    }
}

func main() {
    runtime.GOMAXPROCS(2)
    v := make(map[int][]int64, LEN1)

    for i := 0; i < LEN1; i++ {
        _v := make([]int64, LEN2)
        for j := 0; j < LEN2; j++ {
            _v[j] = int64(j)
        }
        v[i] = _v
    }

    go f()

    for {
        for i := 0; i < LEN1; i++ {
            for j := 0; j < LEN2; j++ {
                v[i][j] += 1
                v[i][j] -= 1
            }
        }
    }

}

操作系统启动后,不启动其它服务,CPU利用率约 1%~2%

测试结果:
程序启动后,2核CPU 全部跑满,GO程序达到 200%,过了两三分钟后,只有一个 CPU 到达 100%,另一个CPU却是 0%,Go程序 CPU利用率 是 100%

Go程序有两个 goruntine,可完全利用 2核CPU,但不知为何另一个 CPU 的利用率突然间变成了 0%

当然,这也有可能是 GO 做了优化(因为其中一个 goruntine 是空循环)!当在 f() 函数中也添加一下逻辑代码,如v := 1; v+=1; v-=1;,CPU利用率始终是 200%

TinKong
#5 TinKong • 2016-11-25 19:04

==按照1楼的1的说法那time包岂不是很影响性能嘛,那获取时间就无解了,第二点的话建议不错,但是服务这样跑很累人。
==2楼 我也开始怀疑人生了,有什么好建议吗
==4楼 你找台8核的跑跑,结果很不一样,全部核都在跑程序。
网上看了这篇文章http://studygolang.com/articles/1855 把GOMAXPROCS设置成 runtime.GOMAXPROCS(runtime.NumCPU() * 2)系统的两倍,同样的环境,16核的cpu一样高,但是播放很流畅了,8核结果cpu略有上升,但是播放效果也是很流畅。初步猜测核数多goroutinue会切换到别的核上运行?????

nuokesasi
#6 nuokesasi • 2016-11-26 20:31

请问楼主用的go版本是哪个版本?

moliliang
#7 moliliang • 2016-11-27 23:50

表示关注该问题

TinKong
#8 TinKong • 2016-11-28 10:15

我用的是1.7

damao
#9 damao • 2016-11-28 20:16

这个不仔细分析代码不太好定位问题,会不会LZ有代码是重CPU消耗的,造成OS线程被某些goroutine长时间占住,别的goroutine不能及时运行,毕竟goroutine不是抢占式的,增大GOMAXPROCS 可能会得到缓解。
另外甩锅给Go不太合适,你用C/C++来写,处理不好多线程并发等等一样出问题

dxhdxh2k
#10 dxhdxh2k • 2016-11-29 00:03

Mark

_v := make([]int64, LEN2)
放在循环外?

stevewang
#11 stevewang • 2016-11-29 09:55
xgfone
#12 xgfone • 2016-11-30 14:18

@dxhdxh2k 这跟我当时的需求场景有关。

我先前曾有这个测试需求:在云平台上的云主机,当配置不足以承担服务需求时,可以热升级 CPU内存,而升级后,新增的 CPU内存 是否会立即被投入使用。

所以,我需要:(1)跑满所有 CPU,(2)将内存使用完,并还要再使用一部分 SWAP 分区中的内存。这样,当从低配置升级到高配置时,我可以清晰的看到新增的 CPU 被立即使用了,而 SWAP 分区中的内存也被移到了新增的内存中。

因此,我需要分配大量的内存。放在循环内,可以循环分配。在 64 位系统上,最终可以分配大约 2~3GB 的内存。

PS:我当时测试的 Case 是,从 1核1GB 升级到 2核4GB,因此,程序使用的CPU核为 2,内存分配了 2GB 多。

a1exander
#13 a1exander • 2016-12-01 15:19

作为一个接触go的新手,我想问一下:
go f() {
for {}
}()
goroutine里跑个死循环,为啥?仅仅为了进程不退出?

bigbear
#14 bigbear • 2016-12-01 23:08

去年看过一篇文章,无限循环中,进行CPU密集运算,有可能导致goroutine无法抢占CPU一直挂起,需要显示调用代码:runtime.Gosched() ,挂起当前goroutine,让出CPU给其他routine执行的机会。

需要 登录 后方可回复, 如果你还没有账号你可以 注册 一个帐号。