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

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

10829 次点击
所在节点    程序员
193 条回复
mightybruce
286 天前
@xiaocaiji111 你不过是螺丝钉而已,别往脸上贴金了, 我他妈笑死了, 这些技术和大公司搞的框架有一个是你搞的,十多年前,java 也没多少解决方案,恰恰那时候是 java 的黄金期
realpg
286 天前
你愿意用 java 你就用 java

反正高并发,我有权进行选型,肯定用 golang 不用 java 的

golang 的高并发项目从来都是内存显得没地方用很闹心,被迫每个 4C8G 的机器就都开个 redis cluster node 省的内存空着看着难受

java 。。。java 的项目组动不动就先开个 64G 内存 然后过一阵子再加到 128G 256G

以上没有任何理论支撑 纯纯自己基于实际情况总结 如果你有异议 那你说得对
realpg
286 天前
实际经验就是:

高并发场景,golang 是指数级的省内存,而且没有那么多经验需要遵守,也不需要去调虚拟机参数

弄只猴子过来都能在一个 4C8G 的节点 写 10K QPS 的 api 服务,而且很多时候,内存都不如 VM 里面的监控用的多
chenxuuu
286 天前
@wysnxzm 你怎么可以这么揭穿他人的双标发言呢,弄得大家多尴尬啊
qcbf111
286 天前
@bthulu 因为 c#不用捧,公认的在 gc 语言中性能最好的一档,语法最优先的之一,奈何早期愚昧失去了生态。语言潜力甚至让 unity 在 c#里面搞了个类 c 语言( dots ecs )。
比他性能高的没他好用,和他差不多好用的没他性能高。
xiaocaiji111
286 天前
@mightybruce 那你就笑死吧,看看 go 除了云原生那些领域,其他哪个解决方案不是餐费的,要么几年不维护,再者有多少经过大公司考验的?除了 cncf ,有几个基金会?有多少行业解决方案,对,没有就是简单。
另外我说了,除了 curd 有很多技术,并没有说是 java ,单单 java 很多也是完不成的,或者说不是好的解决方案。不要为了黑而黑,硬往踩 java 拉 go 上靠。
csys
286 天前
go 吃了 goroutine 设计的亏吧
goroutine 有最小内存负担

nodejs 的 promise 和 C#的 Task 都是简单对象,内存负担很小

tokio 就不太清楚了

这个 benchmark 最不好的地方在于,任务太不实际了,10s 空转,任务本身没有内存负担,又是高耗时
如果一个任务本身的内存负担比较大,那 goroutine 的最小内存负担相对来说就不那么明显了
实际场景里,真正关注的还是吞吐/内存效率
palfortime
286 天前
没有人介绍一下为什么 c#这么优秀吗?
berchtesgaden
286 天前
假设一个 Java 虚拟线程的初始栈也设置为 2K ,100 万个虚拟线程也要用到近 2G 了,为什么这个测试中 Java 只用了 1G 出头?是因为初始栈更小吗?还是测试代码被优化后分配了更少的栈空间?
wysnxzm
286 天前
liuliuliuliu
286 天前
@palfortime 知乎作者原文里有介绍

自古以来的服务器应用都是一台服务器跑一个 server app ,然后让 server app 最大化内存占用,用空间换时间,一台机器撑起大量的流量。所以你会看到 Java 、老版本的 .NET 在 server 上动辄几个 G 的内存占用。
毕竟向操作系统频繁申请和归还内存是一件很消耗性能的事情,因此很多语言都更倾向于一次性 commit 大量内存,然后留着自己管理,即使有可用空间也不会立即进行 decommit 。这样一来当有瞬时的大量内存需求时可以快速地将内存提供给负载用,提升整体性能。
然而云原生时代一切都变了,每一个 server app 都跑在了容器上,由 k8s 等来调度,一台服务器上可能会跑很多的 server app 容器,这就使得以前那种用大量空间换时间的做法失效了:因为占用的资源越少意味着能够调度的容器越多,同时也意味着更容易进行横向扩展,灵活性变的更高。而以前的用空间换时间的做法在云原生时代反而是个累赘。
.NET 自然也是要适应时代变化,因此大概两三年前就开始新的 GC 的设计和实现工作。到了 .NET 9 这项工作终于迎来正式发布,引入了新的 DATAS GC 代替原有的 Server GC 。
这个新的 GC 可以根据实际的应用程序负载来动态调节所需的内存,在不需要的时候快速将多余的内存归还给操作系统,从而大幅度削减内存占用。
然而 GC 并不能坐时光机预测未来的负载如何变化,因此 DATAS GC 下了大量的功夫去设计各种 heuristics ,从而能正确的预测和调节各托管堆大小,让申请下来的内存和实际需要的内存尽可能一致,据说这些 heuristics 甚至用了机器学习来优化过。
在实际测试结果中,DATAS GC 相比原来的 Server GC 节省了 70%~90% 的内存,原来要 1G 内存的 server app 现在只需要不到 200mb 。官方给出的 ASP .NET Core 的 TechEmpower JSON benchmark 测试中,内存占用相比原来更是直接暴降了 93%,直接从原来的 1.6G 降到了现在的 120mb ,要知道 TechEmpower 可是大量并发的压力测试:
wtml
286 天前
@palfortime C#一直很优秀,大神 Anders 的作品,只是吃了出身和早期闭源的亏,要不然哪有 Java 的事,C#可是同时有着 Java 继任者 Kotlin 一样优雅的语法和 JVM 性能的语言
liuliuliuliu
286 天前
不是,你怎么看出来是捧 JAVA 的啊
kneo
286 天前
@csys 我觉这个 benchmark 最有价值的地方就是用 10s 的 sleep ,这样测出来的就是 goroutine 的开销。既然是 benchmark ,我们当然是要得出一个能量化的东西。

有些一直在喷场景不真实的,只能说是顾左右而言他。

最搞笑还有个分享干货教人用 time.AfterFunc 的,拼了命的告诉大家不要在 golang 里同时创建这么多 goroutines 。简直是高级黑。
Gilfoyle26
286 天前
我觉的应该搞成那种公开的挑战赛,规定条件,然后不限语言,类似于游戏的全球排名,然后让个路神仙自由发挥,你行就你上,公开代码,这样大家就没话说了。划定圆圈,自由发挥才是正确的路。
james122333
286 天前
这样比确实不太公平 虚拟线程是真实线程内用队列处理任务 其它的语言用事件驱动也是差不多道理 对标 golang 应该是 goroutine+channel
而 java 内部实现複杂多了 按照惯例 java 依然是写的一坨... 简洁清爽一点更好
CEBBCAT
286 天前


昨天晚上就看见了,当时看见 lesismal 和楼主的交流感觉想说“我倒不会很俗地跳出来说‘好了好了不要吵了’,我觉得看两位的交流也挺能学到东西的”
mightybruce
286 天前
为什么又菜又爱玩的人希望搞各种没意义的 benchmark, 并且这个人很多语言代码都写不对,
目前发这些社区唯一还有点意义的是上次那个 1brc 比赛,
lesismal 是能拿出干货的,比起上面那些只知道搬出社区和框架语言特性的人强出很多, 当然 lesismal 对 go 有偏爱的。
lesismal
286 天前
@wysnxzm #40 所以,抛开两个话题了的具体内容和前提不谈,拿出两段来进行断章取义的对比,你想表达什么逻辑?
lesismal
286 天前
@xiaocaiji111

> 不想跟你对线

是没必要对线

> 建议去大公司体验下,看一个系统除了 curd 以外到底有多少其他技术

这个不用你建议,我就是大公司出身,也是大项目经验,否则我就不搞什么百万连接的解决方案了
我的主要工作不是 curd ,所以看到很多人只用 curd 的角度格局聊技术,不考虑整体工程,也是挺着急的

> 或者去看看 JCP 每次的提案有多少新技术,另外我不评价哪个语言好,哪个语言坏,谁工作中只会只用一门语言的。每个场景都有自己最优的选择。

我也从来没说过 golang 天下第一,我是 c cpp 写的最多,后来 c# node java lua as py 也是项目或者自己玩具一顿用,ruby php 之类的也是稍微玩过,直到用 go 以后就迈不开腿了,不想换其他的语言了。不想换其他语言也不是因为 go 天下第一,而是因为 go 的性能、占用、开发效率、工程性等各方面的平衡

> 只是看不惯上来就无脑说别人拉踩的,反而显得自己是个小丑。

我认为 blog 作者的结论是拉踩,如果你认为不是那就认为不是呗。
如果我认为别人拉踩我就是小丑,那认为”我拉踩我就是小丑“的你也差不多是个小丑吧?没必要这样子

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

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

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

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

© 2021 V2EX