bthread signal & wait 问题咨询
在使用过程中,观察server侧的火焰图,此时brpc_worker thread的cpu基本打满。发现futex_wake、futex_wait占用的时间占比比较高,竞争锁成为了瓶颈。怀疑可能是由于调度不均衡,某些worker中的task较多,而一些worker的task较少,当这些task消费完之后每次都会调用wait,然后又快速被signal唤醒。同时也存在parking_lot中没有wait的线程但仍然在signal的场景。
这方面是否存在一些优化点,比如:
- signal时判断是否存在wait状态的线程;
- wait之前先尝试steal_task n 次,如果都失败再wait挂起线程;或者wait时设置timeout时间而不是每次都是永久wait,只能被signal主动唤醒。
Related issues: #2817 Related PR: #2819 #2398
这个是一个普遍问题,我们线上也遇到了。有状态服务一般会独占机器,现在机器普遍核数都比较多,基本都100+核数了。目前看到brpc在bthread worker比较多的时候调度开销比较大,希望社区能尽快解决一下哈~ ❤️
可以试试分组 https://github.com/apache/brpc/blob/master/docs/cn/bthread_tagged_task_group.md
我看现在parking_lot的数量是写死的,一个tag内只有四个;一旦worker数量多了,futex中锁的竞争就上来了,想问一下这个值改成一个可配的参数或者根据tag所属的worker总数来动态调大会有什么负面影响吗?(比如parking_lot的数量 = bthread_concurrency_by_tag / 4 or 8)。
我自己做了一些测试,除了上述的做法之外,减少无效的signal和在wait前先steal一次也能解决一部分问题。
相同的负载:
启动四个rpc_press
./rpc_press -proto=echo_c++/echo.proto -method=example.EchoService.Echo -input='{"message":"hello"}' -qps=100000 -server=0.0.0.0:8000 -timeout_ms=-1
服务端使用example/echo_c++中的,server,worker线程数量为64个,但是在服务内增加了使用bthread_start_background创建两个bthread来模拟触发signal的行为,优化前后:
前:
后:parking_lot改为16个
可以试试分组 https://github.com/apache/brpc/blob/master/docs/cn/bthread_tagged_task_group.md
分tag得把N个tag(brpc::Server)绑到N个port吧,不太优雅
可以试试分组 https://github.com/apache/brpc/blob/master/docs/cn/bthread_tagged_task_group.md
分tag得把N个tag(brpc::Server)绑到N个port吧,不太优雅
是的,需要这么做,如果各个Server之间没有要求很强的隔离性,那确实也不用这么做。本质上是得优化协程调度。
据我观察,频繁的触发 futex_wake 和 futex_wait 的原因是因为整个系统在“不必要”的时机进入的休眠状态,这样很容易产生一个 wake 和 多个 wait,导致系统调用频繁,worker 越多,wait 和 wake 越频繁,所以减少如何减少这种“不必要性”才是最重要的。我这边经过大量的测试发现一个 case 就是整个系统在高压繁忙下也进入了 wait 状态,很明显这是“不合适的”,“不必要的”。这是由于当前的模型下,会导致 epoll 延后处理,形成了多个 worker 工作在一个 queue 上,queue 没有了,立刻都进入休眠,然后 epoll 被唤醒后,“worker 们”又相继被唤醒,造成了”不必要性“。
据我观察,频繁的触发 futex_wake 和 futex_wait 的原因是因为整个系统在“不必要”的时机进入的休眠状态,这样很容易产生一个 wake 和 多个 wait,导致系统调用频繁,worker 越多,wait 和 wake 越频繁,所以减少如何减少这种“不必要性”才是最重要的。我这边经过大量的测试发现一个 case 就是整个系统在高压繁忙下也进入了 wait 状态,很明显这是“不合适的”,“不必要的”。这是由于当前的模型下,会导致 epoll 延后处理,形成了多个 worker 工作在一个 queue 上,queue 没有了,立刻都进入休眠,然后 epoll 被唤醒后,“worker 们”又相继被唤醒,造成了”不必要性“。
你的PR #2819 先合下?
据我观察,频繁的触发 futex_wake 和 futex_wait 的原因是因为整个系统在“不必要”的时机进入的休眠状态,这样很容易产生一个 wake 和 多个 wait,导致系统调用频繁,worker 越多,wait 和 wake 越频繁,所以减少如何减少这种“不必要性”才是最重要的。我这边经过大量的测试发现一个 case 就是整个系统在高压繁忙下也进入了 wait 状态,很明显这是“不合适的”,“不必要的”。这是由于当前的模型下,会导致 epoll 延后处理,形成了多个 worker 工作在一个 queue 上,queue 没有了,立刻都进入休眠,然后 epoll 被唤醒后,“worker 们”又相继被唤醒,造成了”不必要性“。
你的PR #2819 先合下?
好的,已经在修改中了,刚空出时间来搞
据我观察,频繁的触发 futex_wake 和 futex_wait 的原因是因为整个系统在“不必要”的时机进入的休眠状态,这样很容易产生一个 wake 和 多个 wait,导致系统调用频繁,worker 越多,wait 和 wake 越频繁,所以减少如何减少这种“不必要性”才是最重要的。我这边经过大量的测试发现一个 case 就是整个系统在高压繁忙下也进入了 wait 状态,很明显这是“不合适的”,“不必要的”。这是由于当前的模型下,会导致 epoll 延后处理,形成了多个 worker 工作在一个 queue 上,queue 没有了,立刻都进入休眠,然后 epoll 被唤醒后,“worker 们”又相继被唤醒,造成了”不必要性“。
你的PR #2819 先合下?
好的,已经在修改中了,刚空出时间来搞
啥时候合,再催一下哈哈哈
@zhengJade
@MJY-HUST 可以试试#2907 看能不能解决问题?
没效果
@GreateCode #2819 呢?
这个改动较大,我摘出来测试下
#2819 确实优化了性能,但从测试结果看,也存在调大pthread,同qps消耗cpu变大的情况。 我感觉可能是bthread worker的wait/wake成本过大导致的,比如前者成本是5,bthread处理成本是10,有2个bthread在一个bthread worker处理消耗cpu 是20,但若被其他bthread worker steal走,则成本是10 + (10 + 5) = 25。 所以最好是若bthread worker的bthread queue不长,就别让其他bthread worker steal了,或者queue很长,一次性steal多个,可能bthread latency可能增大,但节省的cpu消耗是更期望。
这是epoll优先处理的优化效果
这是在epoll优化基础上调整pthread的效果
@chenBright 帮忙看看?或者继续优化下?
类似go的 steal half是不是更好一些
类似go的 steal half是不是更好一些
steal 多个的时候,在流量不高的情况下,会造成同一个任务被两个 pthread 反复偷的情况,A 偷了 10 个,做到 一半,可能又被 B 偷回去了。而且由于任务的执行时间长短不同,一刀切的偷多个,可能导致 steal成本增加。
若queue不长(可配),就别唤醒其他bthread_worker了,可以减少上下文切换。 epoll opt优化了很多上下文切换,应该还有优化空间。
flamegraph_process_context-switches_130pthread:
类似go的 steal half是不是更好一些
steal 多个的时候,在流量不高的情况下,会造成同一个任务被两个 pthread 反复偷的情况,A 偷了 10 个,做到 一半,可能又被 B 偷回去了。而且由于任务的执行时间长短不同,一刀切的偷多个,可能导致 steal成本增加。
参考下这里https://github.com/golang/go/blob/go1.24.1/src/runtime/proc.go#L6983
#2819 #2916 #2907 这几个PR能改善这个问题了吗?
我看现在parking_lot的数量是写死的,一个tag内只有四个;一旦worker数量多了,futex中锁的竞争就上来了,想问一下这个值改成一个可配的参数或者根据tag所属的worker总数来动态调大会有什么负面影响吗?(比如parking_lot的数量 = bthread_concurrency_by_tag / 4 or 8)。 我自己做了一些测试,除了上述的做法之外,减少无效的signal和在wait前先steal一次也能解决一部分问题。 相同的负载: 启动四个rpc_press ./rpc_press -proto=echo_c++/echo.proto -method=example.EchoService.Echo -input='{"message":"hello"}' -qps=100000 -server=0.0.0.0:8000 -timeout_ms=-1 服务端使用example/echo_c++中的,server,worker线程数量为64个,但是在服务内增加了使用bthread_start_background创建两个bthread来模拟触发signal的行为,优化前后: 前:
![]()
![]()
后:parking_lot改为16个
![]()
![]()
@MJY-HUST @GreateCode 试试#3033