smallnewer / bugs

18 stars 4 forks source link

多线程性能调优小记 #128

Open smallnewer opened 6 years ago

smallnewer commented 6 years ago

在NodeJS里搞多个Isolate运行于多线程,真是太恶心了。在NodeJS 8+里还不支持开多线程运行多个Isolate。

最近测了下,服务器框架的多线程通信性能实在太差了,差的没法说出口。于是最近抽了点时间尝试解决。 在架构初期,设想着最大的性能消耗应在JSON的解析上,然而实际上并非如此。(实测JSON的编解码一套在850ns/op,实际项目中会比这慢得多)。期间的坑真是有点多。

首先是libgo。在最早的时候设想用libgo的chan做多线程间通信。而且开了3个线程运行其调度器。发现在单核上还是不要开启work_steal算法,该算法会把Isolate运行在不同的线程里,切换的代价过大。当然,这个还是V8和libgo的水土不服。开不开的性能差距还是非常明显的。

就在上面看到希望的时候,发现另外一个坑。框架内消息是通过char 发送的。但是V8的字符串转成char 用到了其StringUTF8Value()。这个在我的机器上实测单次调用在300ns(js->C++空函数在100ns/op,而libgo的chan的通信性能就在140ns/op左右,可以想象一下比用C++开发性能慢了多少),每次发消息都用了好几次,太恐怖。更悲催的是还没找到好的办法解决,那就留着吧,其实最终通信性能能在100W我觉得就足够了(libgo的chan裸通信性能在1000W附近,能到其1/10也够用了)

后来在JS端压测(一来回,100W条消息),确实比之前好一些,无奈底子太差,最终离100W还非常远。

尝试了使用libuv(这个和V8结合应该更好吧。。Node你不就这么用的吗),多线程通信还是慢。用C++的锁+条件变量,一样慢。

后来换了几种不同的实现方式,可能锁碰撞过多,还用了boost的lock free队列(确实有一些效果)。纯C端没问题,无奈只要一到JS端压测,就慢下来。

真正在纳秒级别的操作上,JS还是慢太多了。猜测还是主要在于线程的上下文切换开销,毕竟V8基于线程,而协程的上下文切换要快得多。这个而且没什么盼头。

目前的性能是在,6000ns/op-单核(16W/秒),4000ns/op双核(25W左右)。然而实际中会更慢一些,一者是JSON的编码性能与对象是否hidden class有很大关系;二者是注册回调等运行时开销都在JS端,这个在压测里省去了。

包括压测期的延迟也是一个问题(经常是几十毫秒)。目前也只能开一个WS服务。这一切看起来都不是那么美妙。优化代码改动过多,效果也一般,所以暂时不打算提交到线上。再考虑下其他思路。

smallnewer commented 6 years ago

果然是:柳暗花明又一村。

上周最后搞得头蒙,今晚发现几个问题解决掉。目前为止,JS端要是裸开销(只有空回调函数+发送字符串),可以做到60W条来回/每秒(1600ns/op),每个部分的开销也基本搞清楚,基本没有灰色地带了。

说实话这个结果和协程+channel的方案慢不少(约10~20倍的量级),大部分是在线程开销、纯C++到V8数据格式的转换开销。虽说自己做的消息通道(用vector做的)很初级,但实测发现这里并不比其他人的实现明显的慢。

不过配合JSON格式会慢不少,来回总共两次parse+stringify耗时约在3000ns。果然比压测的1700ns要慢不少,如果是实际生产环境还会更慢。用上JSON压测约在20W/每秒。也算凑合。如果急需优化,JS端至少可以改用BUFFER进行传输,回到60W性能上。

现在延迟还有点略高,需要进一步追踪。

总的来说,一期调优总算是有了一步进展。