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

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

10892 次点击
所在节点    程序员
193 条回复
CloveAndCurrant
295 天前
@kneo 你是真能倒打一耙,“我认为”的是你,嘴硬的是你,抠字眼守旧抱着 wiki 当真理的也是你,然后你把这些臭毛病全都推给别人,自己一身毛硬说别人是妖怪,你是真滴像现在到处碰瓷的老 B 登🤣🤣🤣
kneo
295 天前
@CloveAndCurrant 诶呦,好几天了,咋还是那套车轱辘话啊。我以为你学了啥新东西来怼我呢。也是,您这辈子就这样了,连个术语都改不过来,哪还能学会新东西啊。昨天吃几碗饭没忘就不错了。

不过你总碰瓷 wiki 干啥啊?你脸不要啦?你连个术语都搞错,还代表“工程领域”否定 wiki ,好在没人把你当回事。

你觉得 wiki 是错的,但是你不说你觉得,那你就是对的了?你可真是个小天才哈哈。不知道你家长给你配手表了吗?

你和 wiki 二选一,真不知道整个地球上除了你 MA 还有谁会选你。[捂嘴笑]
CloveAndCurrant
295 天前
@kneo 我能像你这个老古董一样不休息,就靠着这口抱着 Wiki 抠字眼抬杠活着😂😂😂,没水平的老登,早早滚蛋别挡路不好吗🤡🤡🤡
kneo
295 天前
@CloveAndCurrant 你也不会别的啊。上来就输出一个 goroutine 是协程然后还砸脚了,然后说别人“抠字眼”哈哈。然后除了吹牛,又“工程领域”又“wiki 不是真理”的,好像你亲妈给你起名叫真理似是。

信不信让你爸来他都会选 wiki:宁要 wiki 不要逆子。
CloveAndCurrant
295 天前
@kneo 你能不能不要丢人现眼?😅😅😅,Wiki 是让你这货抠字眼的,说你痛点了,就开始满嘴喷粪骂人是吧,真是个小丑🤡🤡🤡。老登你积点德吧
kneo
295 天前
@CloveAndCurrant 哦?

我引用你说 wiki 不是真理?

我说大家 wiki 和你二选一,你开始骂人“老 B 登”。

我说到让你爸来投票,你开始求我别拿 wiki 抠字眼?你是真怕他不选你啊?

我都被你逗笑了,这是你的职业吗?
CloveAndCurrant
295 天前
@kneo 服了,抠字眼叫引用 Wiki ,真能给你满脸皱纹老年斑的脸贴金😅😅😅?老是撒泼打滚骂人,你目前即使无儿无女,你曾经也是又爹有娘吧?不要满嘴喷粪,积点德不好?怪不得别人总是说“不是老人变坏了,而是坏人变老了”
kneo
295 天前
@CloveAndCurrant 哈哈,您真是定义的神啊。您来定义下我是在“引用 wiki”还是在“抠字眼”?


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

However, goroutines are not coroutines (for instance, local data does not persist between successive calls).
CloveAndCurrant
295 天前
@kneo goroutines 不是这种传统无栈协程有什么问题吗? goroutines 是有栈协程啊。你以后别说你看过 Wiki 了,真的是在侮辱 Wiki
kneo
295 天前
@CloveAndCurrant 哈哈,光会说,您倒是引用一个 wiki 说“goroutines 是有栈协程”给我看看啊。需要我帮你吗?
CloveAndCurrant
295 天前
@kneo 你能不能先把你引用的 Wiki 看懂啊😆😆?需要我一口口的喂你?
purplecity
295 天前
java 就是垃圾中的垃圾
kneo
295 天前
@CloveAndCurrant 嘴硬。
CloveAndCurrant
295 天前
@kneo 无知,Wiki 没看懂就火急火燎跟别人对线🤣🤣🤣
bli22ard
295 天前
我不认为这个测试方法不专业,因为它能说明,在这个 sleep 场景下,goroutine 内存占比确实比 java 的 virtual thread 占用高。
我觉的叫 goroutine 为协程并没有什么问题,可能翻译得不够准确,但是当你说 go 协程,别人知道你指的是 goroutine 就可以了,没有什么大问题。

不过我觉得这个测试可能并不能很好的说明问题,不管是 goroutine ,rust 的 tokio ,或者是 c#的 async/await ,它们本质上解决两个问题,一个 io 就绪回调,一个 sleep 就绪回调。但是要实现这种回调,需要在资源没就绪时候,保存现场,腾出 cpu 给其他任务运行,保存完毕后,其他任务运行,资源就绪后,就有了再次被执行的机会。这个内存消耗主要在保存现场需要的内存上。由于这些异步框架实现方式不一,不排除有些实现,检查测试代码不需要保存现场,直接定时器回调实现,从而节省内存。
一种测试方法是通过每个任务去发起一个 tcp 连接,然后服务端 hold 10s 再返回,测试内存占用可能更能说明实际的问题。

另外还有就是执行时间问题,因为如果开一个 goroutine 排队执行,内存占用肯定比开 1m goroutine 小🤣


也有可能 java 的 virtual thread 和 c# async/await 确认非常优秀,全面领先 goroutine ,希望有实力的人从新设计测试方法。
比较好奇,每个 goroutine 2kb ,到底存的是什么
最后 @lesismal @kneo 两位高手来围观
kneo
295 天前
@CloveAndCurrant 哈哈,给你机会了。既然你不会我就帮你个忙。

Wiki 中很明确的提到了 stackful 和 stackless:

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

> Besides that, a coroutine implementation has 3 features:
> - ...
> - ...
> - whether a coroutine is able to suspend its execution from within nested function calls. Such a coroutine is a stackful coroutine. One to the contrary is called stackless coroutines, where unless marked as coroutine, a regular function can't use the keyword yield.

同时也举例了:

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

> Lua has supported first-class stackful asymmetric coroutines since version 5.0 (2003), in the standard library coroutine.

很明显,有栈协程和无栈协程都是协程,不是加个定语就可以随便用的。Wiki 也不是连个有栈无栈都不懂的“过时”信息。

同一页面也明确指出了:

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

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

goroutines 不是协程,加再多定语也没用。

这个总结不是给 @CloveAndCurrant 这种人看的,他除了嘴硬啥也不懂。但我相信其他人看了自然能明辨是非。

我不相信 @CloveAndCurrant 能再给出任何有意义的回复了。不和他聊了。
kneo
295 天前
@bli22ard

> 比较好奇,每个 goroutine 2kb ,到底存的是什么
> 最后 @lesismal @kneo 两位高手来围观

见笑了,谈不上高手。

我的理解这 2KB 就是预分配的栈大小。过小的初始栈可能会导致频繁的内存重新分配。
2KB 可能是 go 团队在性能测试之后得到的一个比较好的默认值。这个值也是经过多次调整的:

https://stackoverflow.com/questions/22326765/go-memory-consumption-with-many-goroutines

> in Go 1.4: the minimum size of the goroutine stack has decreased from 8Kb to 2Kb.
> https://github.com/golang/go/blob/bbd25d26c0a86660fb3968137f16e74837b7a9c6/src/runtime/stack.go#L72:
_StackMin = 2048


也许可以把 2KB 改成 1KB 重新编译一个 go 版本,内存测试的结果就和 Java 一样了。当然前提是 1KB 对这个测试来说够用,够不够用我也不知道……

当然真实环境大家确实不在意一个 goroutine 占用了 2KB 是不是太多。但是这个测试至少让我们知道了这个 2KB 的存在。程序员总是对这种小细节感兴趣的。
orioleq
295 天前
@CloveAndCurrant 说实话你骂得才叫撒泼打滚 @kneo 即使骂人也是有逻辑的,词还蛮新鲜…
CloveAndCurrant
295 天前
@kneo “There is no single precise definition of coroutine”,现代协程概念早就超出这个范围,抠字眼 Wiki 还看不懂😅😅😅
CloveAndCurrant
295 天前
@orioleq 知道了,你是个“受”😂😂😂😂

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

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

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

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

© 2021 V2EX