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

295 天前
 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 更轻量。

10877 次点击
所在节点    程序员
193 条回复
kneo
294 天前
@CloveAndCurrant

goroutine 不是协程不是我定义的,我前面有 wiki 链接。

coroutine 的 co 是 cooperative ,goroutine 是 preemptive 。

哪怕加上“有栈”的定语,它也不是协程。典型的有栈协程应该是 lua 的 coroutine 。
kneo
294 天前
@yuan1028 朋友,你这才是明灯,这才叫“技术干货”。
palfortime
294 天前
刚看了一下代码,这个测试的确有点不公平,rust 在 await 的时候才真正跑。rust 应该要用 spawn ,有人改个加 latch 的版本,看看同时都在跑的时候的占用吗?
sagaxu
294 天前
@lesismal 66#

However, when code running in a virtual thread calls a blocking I/O operation, the Java runtime suspends the virtual thread until it can be resumed. The OS thread associated with the suspended virtual thread is now free to perform operations for other virtual threads.

Java 的 virtual thread 就是为了解决传统线程阻塞时不能让出 CPU 资源提出的方案,如果请求 SQL 就卡住线程了,那就不需要在 JVM 层面提供支持了,完全可以像 Kotlin 一样从 lib 层面支持,调用者自己确保不会阻塞。
CloveAndCurrant
294 天前
@kneo 你认为有个毛用😅,不看实际应用看你认为?在工程领域 goroutine 和 Java virtual thread 都叫有栈协程,Wiki 也是人写的,它也不是什么无上真理。
CloveAndCurrant
294 天前
这个测试虽然有问题,但是 Java 和 golang 我觉得符合预期,Java 的有栈协程实现机制在超大并发的确比 golang 有优势,而对于无栈线程,抛开语言本省内存占用,应该都大差不差。
kneo
294 天前
@CloveAndCurrant 你是不是不认识英文 cooperative ?协程的“协”对你也有点理解困难?你一张嘴说“工程领域就叫有栈协程”,你在工程领域算老几?我还有 WIKI ,你除了你认为你还有毛能拿出来的?
CloveAndCurrant
294 天前
@kneo 怪不得你那么信 Wiki ,自己编的自己信是吧😂,你算老几啊,我说听他是 active ,他就叫活程是吧🤣。我可没说“我认为”,你这个”我认为“怪还倒打一耙了?😂😂😂
dV9zZM1wROuBT16X
294 天前
@grzhan 看了源代码,没有用 sync.Pool ,频繁的开 goroutine ,所以内存占用会比较高,这个测试没有意义

如果真的在乎 java 跟 go 的差距,还不如看 anton putra 的
<amp-youtube data-videoid="PL0c-SvjSVg" layout="responsive" width="480" height="270"></amp-youtube>
CloveAndCurrant
294 天前
@kneo 自己抠字眼,扣的要死,协程这个概念早就超出了当时 cooperative 这个范围了,你还那 Wiki 当工程了,那么死板守旧,回去当老古董不是更好😆😆😆
kneo
294 天前
@CloveAndCurrant 啥也拿不出来在这嘴硬,别误人子弟了。

你知不知道有些错了一辈子的老油条,从一开始就错了,还一直觉得自己是对的,遇到别人振振有词“我们就这么叫”。

我再重复一遍:Goroutine 不是协程。

这句不是写给你看的,因为你老了,改不了了。但总有年轻的有心的。他们会知道你是错的。
thevita
294 天前
这么多 coroutine 的实现, 大概分两类

1. java, golang 有栈实现
2. 其他 无栈实现

要比确实也是 java, golang 单独比合适点

golang 目前版本 使用 initialize size (__StackMin) 为 2k 的 continous stack 作为 coroutine stack

java virtual thread 不太熟,看了点资料,说是 segmented stack ,从表现来看,应该是更灵活粒度更小的
grzhan
294 天前
@Flourite 嗯嗯,我觉得其实不少点在后续诸位老板的讨论输出的知识内容里已经比较清晰了(虽然有不少价值判断与情绪输出 hhh ),这样读到的人在了解之后对于这些编程语言都会有自己恰当的判断。
lesismal
294 天前
@kneo 关于 go 协程这个叫法:
中文英文这些都有很多方言和约定俗成,在非学术环境,尤其是非严谨学术定义讨论的环境,你非要咬学术概念,那我只能认为你是杠精了。随便搜下 go 协程,只要不瞎你应该可以看到大量的标题文章和内容都是这么叫,一些 go 书籍里也是这么叫。如果你觉得这不算社区约定俗成,那请问你 goroutine 中文你给赐个什么名字?或者 goroutime 必须英文不配中文翻译?或者你自己所在的社区不是 go 社区, 至少不包括 go 的中文社区?
别觉得自己知道严格的理论概念就可以拿来咬文嚼字了,这种讨论环境下叫协程大家都知道说的是 goroutine, 就你在那为了杠而杠罢了.
其他一些概念, 例如 tcp 粘包, 我们也知道是错误的定义因为 tcp 本来就是 stream 就不是包, 包是 tcp 之上的层自己的事情, 但是我们讨论问题还是会经常用粘包来描述问题, 因为大家关注的核心点在于这个问题本身而不是这个叫法.
做工程的纠结这些很容易陷入孔乙己思维,茴字写法精通,做工程耽误事挖坑。我见过不少 cpper 对语言语义标准深入学习, 一聊起这些来, 头头是道,但不擅长踏踏实实搞工程实践,眼高手低。
我是搞工程实践的,讲究实践主义实用主义,不是搞咬文嚼字的,没那闲心跟学生在那讨论课本考试题概念定义, 你喜欢的话你继续咬文嚼字好了。
lesismal
294 天前
@kneo
> 你在这刷一天,连个结论都说不出来,我现在告诉你“Java 的 virtual thread 比 go 的 goroutine 成本还要低”,我说了,你反驳我下试试?

说到这个 blog 的结论,我建议你先看看 blog 的标题吧:
How Much Memory Do You Need to Run 1 Million Concurrent Tasks ?

这是测试百万并发任务占用多少内存,而不是百万 go 协程/java 虚拟线程情况下协程和虚拟线程的占用对比,而他的结论呢?
有的语言是说总占用,go 和 java 又谈协程和虚拟线程了,并且得到的结论之一就是你咬住的这个 “Java 的 virtual thread 比 go 的 goroutine 成本还要低”,我说他文不对题不为过吧?
如果是为了这种结论,我建议他把标题直接改成 “Java virtual thread vs goroutine memory cost“。

咱们再回到这个测试的意义上,为什么我说他这个测试没有意义:
1. 百万并发任务的实现有多种方案,通常是为了测出语言能力的上限,对应内存占用,应该是尽量找出消耗更小的实现,而这个测试里的 go 方案就是用最浪费内存的方式
2. 例如 node 这种,本质上就是定时器+回调,java 和 go 是有栈 vthread 和 goroutine ,但不论是 java 还是 go ,也都可以用定时器+线程/协程池的方式去实现,这些不同的实现方案来对比内存占用,不只是对 go 不公平,对 java 也是不公平的
3. blog 作者不同语言采用不公平的方案来测试百万任务,得出虚拟线程和 go 协程占用的对比结论

综上,在多种语言能够对齐实现方案的前提下,为不同语言采用了内存消耗差异巨大的不同方案,然后为了测试一个目的 A ,得出了一些 XYZ 结论。
从标题立意,到过程方法,到结论,驴唇不对马嘴!
说他没意义一点也不为过!

我刷一天得不得出结论不需要你下结论,但是如果前面你说的这些就是你的结论,我建议你,既然聊技术,即使带着情绪,至少也要对技术的部分心平气和点,多用事实说话而不是只输出语气
lesismal
294 天前
@kneo 再说说我为什么提到 nbio

我在 #6 就说过了:

1. 实际场景里也不可能全是 cpu 消耗的 task, 所以 sleep 类型的 task 是合理的
3. 如果 sleep 是为了模拟实际场景里的 io 消耗例如网络 io ...

因为测试是用于给实践作参考,所以看到这些 benchmark 我都会联系实际场景,我看他这个 blog ,会从多方面多个角度去考虑问题,例如前面楼层我讲到的一些,而不是像你那样只看他几个简单文不对题的结论。
#6 里我也给出了前提,基于实践场景的考虑,并且我也从来没觉得 golang 天下第一,因为我知道 golang 的性能不是第一,也知道海量 goroutine 内存占用带来很大压力、不只是内存,还有 gc 和对应的 stw 。
所以我把解决方案带出来,但是巧了,这个方案就是我自己搞的,你可以说我是夹带私货,因为确实是夹带私货,但也正如我前面所说,我把好东西拿出来分享,没什么觉得羞愧的,反倒是觉得光荣,因为我确实给有需要的人带来了帮助。

另外,你说我说的这些技术点不是干货,如果对于你确实不是干货,
首先我为你感到欣慰,因为这说明你基础知识比较好、已经算是基础很不错的同行了,
其次我也为你感到惋惜,因为即便掌握了这么多干货知识:
却不能上来就聊技术的点而是上来就阴阳,
却只是看到别人个 blog 就跟着人云亦云,
却不懂得辩证低综合地实践地去分析和看待技术问题,
却像孔乙己对待茴字一样在这咬文嚼字拿着一个协程概念当成宝,连什么是约定俗成都搞不明白
。。。
kneo
294 天前
@lesismal

> 说到这个 blog 的结论,我建议你先看看 blog 的标题吧:
> How Much Memory Do You Need to Run 1 Million Concurrent Tasks ?

您可下找到重点了。看标题,人家测的就是 Memory ,很明显测一百万是为了知道每个 goroutine (以及其他语言里常见的并发模式)的平均开销。这是这个测试的唯一目的。这个测试结果,不代表 Java 比 Go 强,也不代表 Java 在高性能的表现比 Go 强,连 latency 和 cpu 这种数据都没有,这个测试就是很单纯的一个内存测试,它并不是一个完整的性能测试,但它绝非无效测试。

具体来说,它提供了一个对比数据:一个 goroutine 的内存开销大概是 2KB ,Java virtual thread 大概是 1KB 。

这个测试就是这么简单纯粹,没有一点问题。后面也有人提出了“golang 的 goroutine 是预分配固定大小 2kb 的内存”,和测试完全相符。

您:

> 毫无意义的测试, 却顺便拉踩, 捧 java 踩 go, 实在看不下去了我才必须出来澄清下.

遇到质疑,您的回复是:

> 说句不好听的, 你懂吗? 你懂多少?
> 系统, 底层知识不了解, 只知道看个半吊子脑残的老外的 blog 就在那人云亦云? 还很自豪?

感情我只要“同意”就是人云亦云?和你意见不一样就是“底层知识不了解”?您了解底层也没看您说出来“golang 的 goroutine 是预分配固定大小 2kb 的内存”这种话啊。

还“半吊子脑残老外”这种话,您也说得出口?礼貌吗?看你现在说话收敛了点,冷静了?自己回头看自己的回复脸红不?

> 就是你们这种人多了, 才把技术讨论搞得乌烟瘴气!

信不信,如果你不出现,这个贴子早就在讨论为什么“一个 goroutine 的内存开销大概是 2KB”。

看你最后几个回帖码字不少,虽然观点我不敢苟同,但能看到到你的微妙变化。不再一一针锋回复了。
povsister
294 天前
哇,这帖子也能吵起来。。
一个不专业的测试就不专业呗,你能怎么办?

好为人师的后果:你这人好生无礼。谁叫你多管闲事?
lesismal
294 天前
@kneo #97 和着他测试方法不对齐, 结论文不对题你是一点都不 care 是吧? 就像是, 咱俩要测试 100 米谁跑得快, 我可能跑不过你, 但是让你坐轮椅用手弄轮子向前, 然后我跑赢了说你跑得慢你也觉得合理是吧?
前提和基础都错了, 还在这聊什么结论? golang 协程占用还用聊吗, 大把的帖子文章说过这个事情好些年了, 这还用专门去测才能得出结论?

> 遇到质疑,您的回复是

你不看看为啥我用这种语气是吧? 你在本帖第一句上来是啥方式在回复自己都不记得了是吧?
“半吊子脑残老外”确实是不友善, 但也被你们阴阳怪气在先气到了, 而且也是说说事实罢了, 能写这么篇看上去清晰但实际上没有意义甚至误导还好几个站去推广他的观点, 说他半吊子脑残也是陈述事实, 而且不只是在这个帖子里说, 我这个人比较直接, 在你 #97 回复之前我就已经在 reddit 上去回复他了, 英文我直接说 Stupid :
https://www.reddit.com/r/programming/comments/1h2e35m/comment/lzjwidj/

我也有菜鸡的时候懵逼的时候头昏说错的时候, 发生这种情况别人喷我 sb 等我反应过来我也乖乖承认, 被人说菜说脑残是自己活该, 毕竟误导了别人

什么收敛什么脸红, 扯淡! 之前大部分回复, 没有太过认真编辑, 因为上班时间草草回复了事, 前面你所谓收敛的几个, 是因为下班回家了在这时间充沛, 所以把几个完整的逻辑理清了给你, 要是收敛我就不去 reddit 回复原作者了, 这种 blog 是属于坑小白的误导人的, 就该有人出来指正, 当然, 没被你们阴阳气到的话我也不至于这么不礼貌

至于礼貌, 你们个别人上来不说技术具体的点, 上来就阴阳别人, 这样就礼貌了? 这几年, 不只是 v 站, 各种社媒, 阴阳的人都太多了, 是啥好事情吗? 十句话八句不离阴阳, 有的人甚至张嘴必阴阳, 感情阴阳就是礼貌了? 连技术这种摆在桌面上 1 就是 1 2 就是 2 的问题都不敢直接坦坦荡荡聊非要阴阳, 还跟我谈礼貌, 我的礼貌是留给值得的人的
lesismal
294 天前
@lesismal #99

> 就像是, 咱俩要测试 100 米谁跑得快, 我可能跑不过你, 但是让你坐轮椅用手弄轮子向前, 然后我跑赢了说你跑得慢你也觉得合理是吧?

+ 因为虽然 [ 咱俩都可以双腿正常跑, 且用轮椅跟我用双腿正常跑两者方法不一样 ], 但是, 结论确实是你用轮椅比我正常跑慢 —— 这个就跟 blog 这个测试是一样的, 那我建议你保持逻辑的严谨和一贯性, 所以以后你就自认你自己跑得慢吧

@kneo

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

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

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

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

© 2021 V2EX