运行 100 万个异步并发任务需要多少内存

288 天前
 hez2010

去年有一个 “How Much Memory Do You Need to Run 1 Million Concurrent Tasks?” 的文章测试了各种语言在运行 1 个、1 万、10 万、100 万个异步并发任务下需要多少内存,不过当时测试的版本都很旧,代码里也多多少少有各种槽点,不能反映最新的情况。

这次把所有的语言版本都更新到最新,并且还加入了针对 GraalVM 、GraalVM native-image 和 .NET NativeAOT 的测试,然后修掉了之前被人指出代码中不对的地方,测了一个 2024 年版本的 “How Much Memory Do You Need in 2024 to Run 1 Million Concurrent Tasks?”。

可以在这里看详细测试: https://hez2010.github.io/async-runtimes-benchmarks-2024 。测试环境和代码也在里面有说明。

这里简单贴一下最终的测试结果:

1 个任务,测各语言 runtime 本身的 footprint:

1 万个并发任务:

10 万个并发任务:

100 万个并发任务:

Go 在最开始的时候内存占用很小,但是 Goroutine 的开销非常的大,随着并发任务的数量上升内存大量上涨,个人怀疑是 Go 的 GC 跟不上分配了,估计再接着增大并发数的话 Go 很可能会 OOM 。

Rust 则是发挥稳定,从始至终都表现着非常好的占用水平。

C# 的 NativeAOT 的表现则是直接把 Rust 比下去了,甚至随着并发数量的增大,到后期不做 NativeAOT 的 C# 内存占用都要比 Rust 更小了,可能是因为 tokio 和 async_std 在内存分配和调度这一块儿还有改进空间?

Java 的 GraalVM 表现要比 OpenJDK 差很多,而 GraalVM 的 native-image 表现就要好不少。另外就是忽略 GraalVM 的成绩的话,从结果来看 Java 的 Virtual Thread 要远比 Goroutine 更轻量。

10837 次点击
所在节点    程序员
193 条回复
kneo
288 天前
@mightybruce lesismal 拿出了啥干货吗?你别搞笑了。

他自己的测试结果?那是一个东西吗?那只证明了他搞不清楚原文的并发任务和他自己的并发连接的区别。

time.AfterFunc()?那是一个东西吗,这会减少 goroutine 数量,也就是减少了并发任务。拼了命减少 goroutine 不就是想说 go goroutine 太多了不行?

减少协程数能增加性能这叫干货?啊? Java 用了几十年线程池用他教? Java 现在说可以不用线程池了,他开始把自己祖传的协程池拿出来给别人上课。笑死了。

张嘴闭嘴就自己有干货,回头一看都是啥啊,除了搅乱评论,还真是毫无意义。

从数据中阅读信息是程序员的基本能力。但凡你们有点能力也不至于得出“毫无意义的测试”这种话来。
kneo
288 天前
@kneo 更正下,goroutine 不是协程。被 lesismal 给拐歪了。
mightybruce
288 天前
@kneo 不好意思,time.sleep 是什么,这叫并发任务? 你这么大聪明,建议相互屏蔽。
kneo
288 天前
@mightybruce lesismal 拿出 time.AfterFunc()的时候来我看你也没不好意思啊,跟他上课去吧。
lesismal
288 天前
@kneo #31

> 您还自己 at 上来。

我这个人不阴阳,有事就光明正大地聊,所以直接 at 你了

> 看您一个劲儿贴自己的库,想骗 star 是吧。

你是我用户吗?如果不是,你怎么知道我是骗 star 呢?
有实际用户在生产中使用,或便利,或提升,用户 issue 我都尽量详细回复,bug 我都努力修复,如果这都算骗 star ,那我没法跟你争论,因为你的水准实在太“高”了

> 我看了一眼,请问您这写个破库是干嘛的呢?能解决人家说的一百万个并发任务吗?不会是解决不了得靠排队了吧。您不会想说现实中大家都是排队的吧?您不会说别人不排队就能轻松并发是作弊,毫无意义吧?

这个涉及知识点,我继续认真给你聊.
不同语言的线程池/协程池/任务队列的排队是不一样的。

以 4c8g 的规格为例。

比如传统方式的 c/c++,100 个逻辑线程池 worker (或者多进程)+队列的方式,你的并发只有 100 ,如果任务里 io 或者 sleep 之类的,单个线程就被占用了,如果这组逻辑线程池全都执行这样的任务,就全同时都长时间阻塞了,cpu 不能得到充分有效利用。
因为 8 或者 100 这个并发度太低了,但是进程或者线程的成本太高了,普通配置的节点没法创建过多的进程或者线程。
所以这种传统方案要想高性能就得回调的方式。c/c++的协程库也有不少,但需要手动,代码比 go func 更复杂罢了

但是 go 协程的成本低,4c8g 这种 1-10w 个协程,runtime 也是 ok 的,调度和内存压力都不算大。这个并发度就可以是 10w 。
实际业务中,从上游到下游本身也会有访问限制,类似“限流”的机制,比如下游的数据库,上游如果 10w 并发都同时去请求数据库,数据库也受不了,所以数据库的使用通常都是长连接+连接池,连接池 size 有限,即使上游调用 sql 并发量吵了也要等空闲连接,而不是直接都丢给数据库把数据库干死
但 10w 并发本身并发度足够高,即使部分协程等待 sql 连接阻塞了出让 cpu 了,但并不是把线程阻塞了,runtime 会调度其他协程,不会让 cpu 干等着
然后业务上的协程池和下游资源动态平衡
具体需要多少并发度需要多大连接池 size 或者其他下游资源的类似”限流“配置,要自己项目实际情况来定,这里的 1-10w 只是举例子


> 说句不好听的,您连别人测的是啥都看不懂。

你看懂了?你觉得他这个 benchmark 很有意义?那我对你的自信表示万分钦佩。。。

> 人家的百万并行任务和你的百万连接是一个东西吗。但是不影响你有个什么破库,在那 BB 好像自己懂似的。在那一顿输出自己的“真才实学”,一边喷人“毫无意义”,

我说这俩一样了吗?但是 benchmark 是用来干嘛的?是用来为实际工程做参考的,联系实际业务场景扩展相关内容,不行吗?
分享自己的库的问题,我在 #34 结尾说了
我是在给开源社区做回报,给我的库的用户带来价值,你认为破还是不破是你的主观是你的自由,但不代表别人,所以我也根本不 care 你
lesismal
288 天前
@kneo #64

> lesismal 拿出 time.AfterFunc()的时候来我看你也没不好意思啊,跟他上课去吧。

有什么不好意思的,比如 node 的测试代码,本质上就是定时器回调;
rust 和 py 的代码本质上应该也是类似的,如果不是类似、而是真的用 sleep ,那这个测试 rust 要跑很久了;

然后 go 这就不让用定时器回调、必须要用大量有栈协程是吧? time.AfterFunc 到时间了也是起个协程来执行的,但是是用的时候才起来,定时的功能没必要在到时之前就创建大量的协程去等待、白白浪费资源

非要用这种代码做这种测试,你还要赞同他的测试,如果所有业务都像你这么搞,那你们老板也是挺舍得花冤枉钱的啊


java 的 VirtualThreads 我不了解别后机制不乱说,另外就是,上下游的各种基础设施,比如请求 sql 是否也是 jvm 支持了出让 cpu 之类的,会不会导致线程阻塞的问题
这些需要上下游全套 io 相关之类的都避免线程阻塞才有更大意义,否则一个链条线程卡住了,就导致大量排队等待、cpu 利用不起来、性能起不来了
有哪位懂的可以讲解下让我顺便学习下
kneo
288 天前
@lesismal

数据你不会看是吗?都画成图了你也看不懂是吗?

你一口一个 go 协程( goroutine 不是协程),说 goroutine 成本低,原帖的结论之一是 Java 的 virtual thread 比 go 的 goroutine 成本还要低。用最简单的代码,得出最简单的结论。

我也不需要说测试结果仅供参考之类的。我可以告诉你,这就是结论。请问你拿什么反驳?你在这显摆你那破库?你那破库证明了 goroutine 比 virtual thread 的成本低了吗?

你那破库顶多能说明,生产环境里,哪怕 goroutine 比 java 的 virtual thread 成本高,也一样能实现高性能应用。当然能啊,Java 没有 virtual thread 不也主宰行业二十年?你拿出来的玩意,对这个讨论一点帮助都没有。除了自我营销和认知水平有限之外,我想不出来你在这捣乱的理由。

你这种水平的作者,写的库我不也 care ,但是得放在心里,避个雷。我刚才说看了一眼是逗你玩的。你这种库我看名字就知道干啥的。你还真以为帮你贡献点击量了?
mayli
288 天前
C# 的 NativeAOT 真的屌
Python 没有特别拉跨倒是出乎意料
lesismal
288 天前
@kneo #67

> 数据你不会看是吗?都画成图了你也看不懂是吗?

和着我们说的你都不看是吧?前提和方法错了,还需要看他的测试数据?走错方向了然后说走了很长距离所以虽然方向错了但是很正确很牛逼?

> 你一口一个 go 协程( goroutine 不是协程)

这里用的是生产实践约定俗成的概念为准,你不用咬理论概念,你当这写论文呢? go 社区很多人把 goroutine 叫协程是约定俗成的事情,较这个真有意思?
另外看了眼 wiki 里支持协程的语言列表里也有 go ,你要是非拿着早期一些老爷子的协程概念说事,我建议你把每个语言的协程定义都规范下然后去找各个语言社区推广下看看有没有人乐意花时间跟你咬文嚼字

> 说 goroutine 成本低,原帖的结论之一是 Java 的 virtual thread 比 go 的 goroutine 成本还要低。用最简单的代码,得出最简单的结论。

我说 goroutine 成本低是对比进程线程,我说 goroutine 比 java birtual thread 成本低了吗?你这咋还断章取义移花接木呢?
我说的是他这个测试没意义,结论里虽然没明说,但却是相当于拉踩
原来你就喜欢没意义的测试结论是吧?那你去给作者点赞吧,这种拉屎的 blog ,我是欣赏不来
kneo
288 天前
@lesismal #66 差点被你逗笑了。因为语言的底层实现不一样,所以你就抱怨不公平?还真是委屈你家的 Go 了……咱们以后不比了。
kneo
288 天前
@lesismal #69

管 goroutine 叫协程是你的叫法,我们社区不这么叫,别给我们抹黑。叫错了就是叫错了,非在这嘴硬?能不能实事求是点?

你说“看了眼 wiki 里支持协程的语言列表里也有 go”,我也去看了下,

https://en.wikipedia.org/wiki/Coroutine#Go

However, goroutines are not coroutines (for instance, local data does not persist between successive calls).

怀疑你有没有点基本的信息检索能力。怪不得看不懂别人的测试结果。

嘴硬+阅读能力差,您这 buff 叠满了。
CloveAndCurrant
288 天前
@kneo 叫协程也没啥错,所谓 goroutine 就是有栈协程,协程本身就包括有栈和无栈的。叫 goroutine 就是为了区分一下吧
Kauruus
288 天前
所以一个 goroutine 大约用 (2641592 - 3644) / 1000000 = 2.64 KB 内存?应该还有些 timer 的消耗在里面。

然后 Java virtual thread 是 (1117640 - 46304) / 1000000 = 1.07 KB ?是因为默认的栈更小还是其他原因?
kneo
288 天前
@lesismal

> 我说 goroutine 成本低是对比进程线程,我说 goroutine 比 java birtual thread 成本低了吗?

我说你说了吗?

你在这刷一天,连个结论都说不出来,我现在告诉你“Java 的 virtual thread 比 go 的 goroutine 成本还要低”,我说了,你反驳我下试试?

你还“对比进程线程”,你和 Java 8 对比啊?人家测的是 JDK 21 。你要是虚心点看看人家帖子还能学点东西。

说人家“捧 java 踩 go”,你要像人家一样实事求是贴代码贴数据一步步写结论,没人和你杠。你在这瞎喷还夹带私货,顾左右而言他,还说自己“有技术干货”。

你知道吗,我“实在看不下去了”,我“必须出来澄清下”。
yuan1028
288 天前
哈哈哈吵的很有意思,但没必要拉踩语言。我也对 java python 为什么比 go 的内存消耗更小比较好奇?
然后看了文章提到之前的测试,文章下边有评论说的比较清楚: https://pkolaczk.github.io/memory-consumption-of-async/

With no doubt, pre-allocating a stack for each goroutine gives Go a disadvantage over languages whose concurrency system postpones any thread-local memory allocation until really needed. (Side note: I use "thread" as a synonym for green or virtual threads and goroutines in this particular context.)

golang 的 goroutine 是预分配固定大小 2kb 的内存,而 java 的虚拟线程不是预分配,而是根据实际需要的大小去分配。

感觉可以测试函数里,创建一个大的 map ,来占用内存,看看更加真实接近的内存使用(还能看看 VM 语言的 GC 水平)

又学到新知识了!
CloveAndCurrant
288 天前
@kneo 你从哪个得出“Java 的 virtual thread 比 go 的 goroutine 成本还要低”这个结论的?😒,虽然都是有栈协程,但是两者实现分配内存机制都不一样,成本低不低要看实际应用情况啊,不能一概而论🙂
ninjashixuan
288 天前
这种赛博斗蛐蛐的测试也能吵起来。
kneo
288 天前
@CloveAndCurrant 有栈没错,但 goroutine 不是协程啊……
CloveAndCurrant
287 天前
@kneo 定义的神😅
kneo
287 天前
@CloveAndCurrant 实际生产环境的性能表现当然会有各种干扰,没有任何单一指标能决定一切,但不意味着我们不去对单一指标进行量化。Benckmark 的作用之一就是去除变量。

go 和 java 的优劣固然不能一概而论。但“Java 的 virtual thread 比 go 的 goroutine 成本还要低”这点,我认为是结论。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1093492

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX