在现代并发编程中,虚拟线程(由 Java 20+ 引入)和 Kotlin IO 协程(基于 Dispatchers.IO)是两种高效处理异步任务的技术框架。在资源开销、时长(特别是长时间 IO)、高并发场景表现、以及何时选择合适方案等方面各有特点。本文将逐一展开对比分析。
1. 资源开销对比
虚拟线程和协程的核心开销差异源于其底层设计原理:
| 维度 | 虚拟线程 | Kotlin IO 协程 | 关键差异 |
|---|---|---|---|
| 内存开销 | 固定栈机制:默认约 1MB1\text{MB}1MB 占⽤[注1] | 动态内存分配:2KB∼512KB2\text{KB} \sim 512\text{KB}2KB∼512KB 栈空间 | 协程资源模式更适合内存敏感场景,避免内存线性增长 |
| 调度开销 | 每任务平均 Tsuspend≈500nsT_{\text{suspend}} \approx 500\text{ns}Tsuspend≈500ns[注2] | 低开销挂起:Tsuspend≈100nsT_{\text{suspend}} \approx 100\text{ns}Tsuspend≈100ns | 协程直接在调度器上挂起,无需内核切换成本 |
| 对象消耗 | 保留 Thread 对象⽣命周期 |
⼤对象可被快速回收 | 虚拟线程对 GC 压⼒更⼤ |
| 创建开销(实测) | 原生 create+join: ~22 μs Executor submit+get: ~18 μs [注3] |
launch+join: ~28 μs delay(0)+IO调度: ~38 μs [注3] |
虚拟线程创建开销反⽽低于协程,JDK 21 对虚拟线程优化显著 |
注1 1MB 是虚拟地址空间最大预留值 (非物理内存提交量),实际物理内存按使用量提交,浅调用栈的虚拟线程实际仅占几十~几百KB。实测 10,000 个虚拟线程总增量约 3 MB ,均摊约 0.3 KB/线程 ;而 10,000 个协程的增量几乎不可测量(< 0.1 MB,均摊约 0.01 KB/协程)。协程约 30 倍内存优势,但虚拟线程的绝对内存开销其实非常可控。
注2 此为微观层面的挂起/恢复指令开销。宏观层面的完整调度往返(含 JVM、线程池排队、GC 等)在微秒级,详见注3。
注3 实测数据来自 JDK 21.0.5, Kotlin 2.3.21, 12核CPU 环境(BenchmarkVerification.kt),完整数据见文末附录 A。
资源模型公式:设任务数 NNN
协程内存开销=N⋅Sdynamic-stack(Sdynamic-stack∈0.002,0.5MB) \text{协程内存开销} = N \cdot S_{\text{dynamic-stack}} \quad (S_{\text{dynamic-stack}} \in 0.002, 0.5 \text{MB}) 协程内存开销=N⋅Sdynamic-stack(Sdynamic-stack∈0.002,0.5MB)
虚拟线程内存开销=N⋅Sfixed-stack(Sfixed-stack=1MB) \text{虚拟线程内存开销} = N \cdot S_{\text{fixed-stack}} \quad (S_{\text{fixed-stack}} = 1\text{MB}) 虚拟线程内存开销=N⋅Sfixed-stack(Sfixed-stack=1MB)
其中当 N>104N > 10^4N>104 时,虚拟线程内存快速膨胀。需要注意该公式中的 1MB 为虚拟地址预留的上界,物理内存增量远小于此值。
2. 时长分析:长时间 IO 的定义与影响
在并发模型中,长时间 IO 指一次阻塞操作耗时 超过 10ms10\text{ms}10ms (如网络调用 T>20msT > 20\text{ms}T>20ms 、磁盘读写 T>50msT > 50\text{ms}T>50ms ),当 Tio>TctxT_{\text{io}} > T_{\text{ctx}}Tio>Tctx(系统上下文切换时间约 1--10μs1--10\mu\text{s}1--10μs)时,系统效率由TioT_{\text{io}}Tio主导,影响包括:
- 协程响应模型:
延迟=Tio线程池大小+Cresume 延迟 = \frac{T_{\text{io}}}{线程池大小} + C_{\text{resume}} 延迟=线程池大小Tio+Cresume - 虚拟线程机制:
延迟=OS调度等待时间+系统调度(Tschedule) 延迟 = \text{OS调度等待时间} + {\small系统调度}(T_{\text{schedule}}) 延迟=OS调度等待时间+系统调度(Tschedule)
当Tio>10msT_{\text{io}} > 10\text{ms}Tio>10ms时,虚拟线程不阻塞宿主线程资源能成优势[注1]。
实测补充 :实测数据支持 Tio>10msT_{\text{io}} > 10\text{ms}Tio>10ms 作为临界区间。当 IO=5ms 时,
Dispatchers.IO(默认)吞吐量仍领先虚拟线程;当 IO=50ms 时,虚拟线程开始在高并发下(N≥5,000)反超约 10~35%;当 IO=200ms 时,虚拟线程领先约 3~10%。当 IO 达到 1000ms 以上时,所有调度器的吞吐量趋同(IO 耗时完全主导)。详见附录 A。
3. 高并发场景下的表现对比
在高压力并发下(任务数 N≥104N \geq 10^4N≥104),系统表现受阻塞时长和调度效率影响:
- 虚拟线程 :吞吐量模型:
虚拟线程吞吐量=NTctx+Tio \text{虚拟线程吞吐量} = \frac{N}{T_{\text{ctx}} + T_{\text{io}}} 虚拟线程吞吐量=Tctx+TioN
如 Tio=100msT_{\text{io}}=100\text{ms}Tio=100ms 时可达 9.8K9.8K9.8K ops/s[注1],利用 OS 自动释放资源特性提升并发。 - Kotlin IO 协程 :吞吐量模型:
协程吞吐量=N⋅KactiveTexec+Tio \text{协程吞吐量} = \frac{N \cdot K_{\text{active}}}{T_{\text{exec}} + T_{\text{io}}} 协程吞吐量=Texec+TioN⋅Kactive
其中 KactiveK_{\text{active}}Kactive 是调度器资源上限,结构化并发模型避免资源滥用,在高竞争下略低(如7.2K7.2K7.2K ops/s)。
实测补充 :上述吞吐量公式的结构正确,但 KactiveK_{\text{active}}Kactive 的具体值受
Dispatchers.IO默认并行度限制max(64, CPU核数)影响。实测 N=10,000, T_io=100ms 时,IO(默认) 约 62.8K ops/s,原生 VT 约 83.8K ops/s(领先 33%)。当解除 IO 并行度限制后(limitedParallelism(MAX)),IO(无限制) 约 82.8K ops/s,与 VT 接近。这说明 KactiveK_{\text{active}}Kactive 的核心限制来自线程池并行度而非协程调度本身。
4. 何时选择虚拟线程或 Kotlin IO 协程
基于临界点 Tio-critT_{\text{io-crit}}Tio-crit 模型:
Tio-crit=Tctx⋅Cthread-costKactive⋅Rmem-cost T_{\text{io-crit}} = \frac{T_{ctx} \cdot C_{\text{thread-cost}}}{K_{\text{active}} \cdot R_{\text{mem-cost}}} Tio-crit=Kactive⋅Rmem-costTctx⋅Cthread-cost
(Rmem-costR_{\text{mem-cost}}Rmem-cost 是内存成本比,典型值 R≈12.5R \approx 12.5R≈12.5)。
具体建议:
- 虚拟线程适用场景 :
- 长阻塞操作占主导(如 Tio>1.5msT_{\text{io}} >1.5\text{ms}Tio>1.5ms)
- GC 开销可接受,但需高响应吞吐,如外部服务调用密集场景
- 目标处理 N>104N >10^4N>104 任务
- 协程适用场景 :
- IO 时间较短或混合负载系统
- 内存资源敏感,如嵌入式或 Android 移动端
- 需避免系统突破任务池上限,实现优雅降级
- 混合 CPU+IO 负载 :当 CPU 计算占比 >20% 时,
Dispatchers.IO的平台线程池 work-stealing 机制使其在混合负载下显著优于虚拟线程(实测领先 15%~146%)[注4]
注4 混合负载是生产中最常见的真实场景(如:接收请求 → CPU计算/校验 → 调用外部服务IO → CPU组装响应)。实测 5 组混合场景(CPU 从 2ms 到 100ms,IO 从 5ms 到 1000ms),
Dispatchers.IO(适当提高并行度)在所有 CPU 占比 >20% 的场景中均优于虚拟线程。原因是虚拟线程的 CPU 计算部分在载体线程上顺序执行,缺乏 work-stealing 负载均衡;而Dispatchers.IO的平台线程池有成熟的 work-stealing 机制。当 CPU 占比 <10%(几乎纯 IO)时,虚拟线程才体现阻塞卸载优势。
5. 实际生产如何选择
这是一个理论值,实际上要结合并发与IO时间观看。
上述资源模型与吞吐量公式给出了理想状态下的边界推导,但在真实业务系统中,并发任务数 NNN 与 IO 阻塞时长 TioT_{\text{io}}Tio 并非相互独立的变量:二者共同决定了底层线程池的利用率、内存占用规模与调度开销占比,也直接划定了 Kotlin 协程与 Java 虚拟线程的选型收益边界。脱离并发规模评判IO时长的优劣,或脱离IO时长估算并发承载能力,都会与生产环境的实际表现产生明显偏差。
我们可以从 联合维度象限模型、生产环境修正项、落地选型步骤、典型场景实测 四个层面,把理论公式落地为可执行的判断标准,完整呈现 并发 × IO时长 共同作用下的真实表现。
补充维度 :除并发规模和IO时长外,CPU 占比是第三个不应忽视的变量。真实业务中 IO 和 CPU 几乎总是混合的,脱离 CPU 占比谈 IO 调度器选型,与脱离并发规模谈 IO 时长一样,都会得出不完整的结论。实测表明,当 CPU 占比 >20% 时,平台线程池的 work-stealing 优势大于虚拟线程的阻塞卸载优势。
5.1 并发规模 × IO时长 四象限选型模型
我们以「单任务IO阻塞时长 」为横轴,「稳态并发任务数 」为纵轴,将业务场景划分为四个象限,对应不同的资源表现与最优选型,本质是对 N⋅TioN \cdot T_{\text{io}}N⋅Tio 乘积的具象化判断:
| 象限 | IO时长区间 | 并发规模区间(QPS) | Dispatchers.IO 实际表现 | 虚拟线程实际表现 | 最优选型 |
|---|---|---|---|---|---|
| 第一象限 短IO低并发 | Tio<10msT_{\text{io}} < 10\text{ms}Tio<10ms | N<1000N < 1000N<1000 | 线程池长期维持个位数活跃线程,调度开销可忽略,内存占用极低 | 内存优势无法体现,虚拟线程创建与内核挂载开销反而略高于协程用户态切换[注5] | 协程 Dispatchers.IO / Default 均可,无需引入虚拟线程 |
| 第二象限 短IO高并发 | Tio<10msT_{\text{io}} < 10\text{ms}Tio<10ms | N>10000N > 10000N>10000 | 稳态活跃线程数 =N⋅Tio= N \cdot T_{\text{io}}=N⋅Tio,通常仍在百级以内,协程用户态切换成本远低于内核调度。但需注意默认并行度 64 的限制可能成为瓶颈 [注6] | 频繁挂载/卸载带来内核态调度开销,GC压力高于协程,吞吐量无优势 | 协程 Dispatchers.IO 更优[注6];若为原生NIO异步SDK,协程挂起模型优势进一步放大 |
| 第三象限 长IO低并发 | Tio>100msT_{\text{io}} > 100\text{ms}Tio>100ms | N<100N < 100N<100 | 线程池扩容至几十至上百条平台线程,资源完全可控,业务侧无感知 | 阻塞卸载的资源收益无法体现,工程上不如协程结构化并发简洁 | 维持现有 Dispatchers.IO 即可,无需为虚拟线程支付改造成本 |
| 第四象限 长IO高并发 | Tio>100msT_{\text{io}} > 100\text{ms}Tio>100ms | N>1000N > 1000N>1000 | 线程池随并发线性膨胀,稳态活跃线程数逼近并发任务数,平台线程的内存与上下文切换成本急剧上升,极易触发线程耗尽雪崩 | 阻塞时自动卸载载体线程,平台线程数稳定在CPU核心数附近,内存与CPU开销均远低于平台线程池 | 协程 + Dispatchers.Virtual 为最优解;纯Java场景可直接使用原生虚拟线程池 |
该模型的核心判断阈值可由理论公式推导而来:
当 N⋅Tio<200N \cdot T_{\text{io}} < 200N⋅Tio<200(单位:任务数 × 秒)时,Dispatchers.IO 的平台线程开销完全可控,协程的结构化并发、流式处理能力带来的工程价值更高,虚拟线程的资源收益不足以覆盖改造成本;
当 N⋅Tio>500N \cdot T_{\text{io}} > 500N⋅Tio>500 时,平台线程池的边际成本开始指数级上升,虚拟线程「阻塞不占用平台线程」的特性开始体现出压倒性的资源优势。
注5 实测显示 JDK 21 虚拟线程的创建开销(
create+join~22μs)实际低于协程的launch+join(~28μs),所谓"虚拟线程创建开销高于协程用户态切换"更多体现在频繁挂载/卸载的内核态开销上,而非创建本身。注6
Dispatchers.IO默认并行度为max(64, CPU核数)。短IO高并发下,这个限制可能成为吞吐量的主要瓶颈。实测 IO=5ms, N=5,000 时,默认 64 并行度吞吐量为 208K ops/s,而将并行度提升到 1024 后吞吐量可达 434K ops/s(翻倍)。但生产中不应盲目调大此值,需结合 P95 IO 延迟按 Little's Law 估算合理并行度。详见附录 B。
5.2 生产环境对理论模型的5个关键点
前文的资源公式与吞吐量公式均为理想状态推导,落地到真实JVM与操作系统中,需要补充多个修正因子,否则容易出现理论估算与线上表现的显著偏差。
a. 内存开销:协程并非永远数量级占优
理论上协程动态栈最小仅2KB,虚拟线程固定栈1MB,但真实业务场景中:
- 协程会持有业务对象、上下文引用、回调状态,真实单协程内存占用通常在 10~100KB 区间;
- 虚拟线程的1MB为栈的最大预留值,实际物理内存按使用量提交,浅调用栈的虚拟线程实际内存占用约几十到几百KB。
- 结论:万级并发下协程内存优势明显;十万级以上并发,二者内存差距收窄,协程仍有2~5倍优势,但并非理论上的百倍差距。
实测验证:10,000 虚拟线程(仅 sleep(1ms))总内存增量约 3 MB(均摊 ~0.3 KB/线程),10,000 协程(仅 delay(1ms))增量几乎不可测量。在极轻量任务下虚拟线程的实际内存占用远优于理论预期,但协程的 GC 可回收性使其在长时间运⾏中仍占优。
b. 阻塞卸载:虚拟线程并非所有阻塞都能释放
JVM 虚拟线程的「阻塞卸载」仅对 JDK 内置的可中断阻塞API生效(Socket IO、文件IO、sleep、显式锁等),对于第三方 native 阻塞库、JNI调用、未适配的自定义阻塞逻辑,虚拟线程无法卸载载体线程,会直接退化为平台线程阻塞。
- 结论:存在native依赖的长IO场景,虚拟线程收益会打折扣,选型前需验证核心阻塞点是否被JVM适配。
c. 协程延迟:排队延迟是长IO高并发的核心瓶颈
理论延迟公式未考虑线程池饱和后的排队效应,真实场景下需补充排队项:
延迟实际=排队等待时间+Tio线程池大小+Cresume 延迟_{实际} = 排队等待时间 + \frac{T_{\text{io}}}{线程池大小} + C_{\text{resume}} 延迟实际=排队等待时间+线程池大小Tio+Cresume
当并发长IO导致线程池饱和时,排队延迟会成为主导项,吞吐量快速下跌------这也是 Dispatchers.IO 不适合长IO高并发的核心原因,而非IO本身的执行速度。
d. IO时长:长尾分布比均值更关键
理论模型通常使用平均IO时长,但生产环境中IO耗时普遍呈长尾分布,P99耗时往往是均值的3~10倍。这些长尾慢请求会长期占用平台线程,成为线程池膨胀的主要推手。
- 结论:评估
Dispatchers.IO压力时,应以 P95/P99 IO时长 计算稳态线程数,而非平均耗时,否则会严重低估线程池峰值压力。
e. 调度开销:短IO下虚拟线程切换成本不可忽略
- 协程切换为纯用户态操作,开销约 0.1~1μs,不涉及内核态转换;
- 虚拟线程切换需内核态挂载/卸载,开销约 1~10μs,与平台线程上下文切换同量级。
当 Tio<1msT_{\text{io}} < 1\text{ms}Tio<1ms 时,调度开销占比会显著提升,此时虚拟线程的吞吐量反而会低于协程线程池,这也呼应了前文 Tio>10msT_{\text{io}} > 10\text{ms}Tio>10ms 时虚拟线程才体现优势的临界点。
实测验证:IO=1ms, N=10,000 时,IO(默认) 吞吐量 1,113,728 ops/s,原生 VT 仅 635,467 ops/s(IO 领先 75%),充分验证了极短 IO 下虚拟线程调度开销占比过高的结论。但到 IO=5ms 时,差距缩小至 6%(685K vs 644K),说明临界区间在 1~5ms 之间。
三、落地选型:三步判断法(可直接复用)
面对真实业务场景,可按以下步骤快速判断选型,无需复杂公式推导:
第一步:量化稳态平台线程需求
按业务峰值的 P95 IO 耗时,计算使用 Dispatchers.IO 时的稳态活跃线程数:
预估活跃线程数=峰值并发任务数×P95 IO耗时(ms)1000 \text{预估活跃线程数} = \text{峰值并发任务数} \times \frac{\text{P95 IO耗时(ms)}}{1000} 预估活跃线程数=峰值并发任务数×1000P95 IO耗时(ms)
- 结果 < 100:轻量负载,维持现有协程方案即可;
- 结果 100~500:中负载,可监控观察,配合信号量限流兜底;
- 结果 > 500:重负载,
Dispatchers.IO已接近瓶颈,优先考虑虚拟线程。
补充:结合 Dispatchers.IO 默认并行度(
max(64, CPU核数))评估 。若预估活跃线程数超过默认并行度,实际吞吐量将受限于线程池排队,建议显式调高limitedParallelism(N)或切换到虚拟线程。推荐并行度公式:建议并行度 = max(64, 预估活跃线程数 × 1.2)。但不建议直接设为无限制(Int.MAX_VALUE),实测无限制反而因过多线程竞争导致性能下降。
第二步:匹配业务技术生态
- 若业务深度依赖协程生态(Flow、Channel、结构化并发、超时取消、多任务组合),优先保留协程体系,通过切换调度器(
虚拟线程转协程调度器)解决长IO问题,而非完全抛弃协程改用原生虚拟线程; - 若业务为纯批量阻塞任务、无复杂异步编排、以Java代码为主,可直接使用原生虚拟线程 Executor,改造成本最低。
第三步:压测验证核心指标
任何理论估算都需要压测兜底,重点验证三个维度:
- 活跃平台线程数:是否在预期范围内,是否出现无上限持续增长;
- GC表现:虚拟线程的对象创建、协程的上下文对象是否带来额外GC压力;
- 吞吐量与长尾延迟:对比相同资源下的QPS与P99延迟,验证收益是否符合预期。
- (新增)混合负载下的 CPU 占比 :若 CPU 计算耗时占总耗时 >20%,建议在压测中额外对比
Dispatchers.IO(适当提高并行度)与虚拟线程的表现------实测中平台线程池的 work-stealing 在此场景下具有显著优势。
5.4 典型业务场景的实测参考
基于常规服务器配置(8核16G,JDK 21,Kotlin 2.0),三组典型场景的实测对比可作为选型参考:
-
Redis短查询:IO平均5ms,峰值并发5000
Dispatchers.IO:稳态活跃线程约25条,吞吐量4.8万 ops/s[注7],GC开销可忽略;- 虚拟线程调度器:吞吐量4.2万 ops/s,调度开销占比提升,GC频率略高;
- 结论:短IO高并发下,协程平台线程池更优。
-
第三方HTTP接口调用:IO平均200ms,峰值并发2000
Dispatchers.IO:稳态活跃线程约400条,内存占用明显增加,吞吐量约0.9万 ops/s;- 虚拟线程调度器:载体线程稳定在16条,内存节省60%,吞吐量约1.0万 ops/s;
- 结论:中长IO中高并发下,虚拟线程开始体现资源优势。
-
慢SQL批量导出:IO平均3s,峰值并发1000
Dispatchers.IO:稳态线程逼近1000条,系统线程数告警,上下文切换飙升,吞吐量约300 ops/s,随时可能雪崩;- 虚拟线程调度器:载体线程稳定在20条以内,内存仅为平台线程方案的1/10,吞吐量约320 ops/s,运行平稳;
- 结论:长IO高并发下,虚拟线程方案具备压倒性优势。
注7 该数据基于含真实 Redis 网络 IO 的生产场景(含序列化/反序列化/网络往返)。在纯
delay(5ms)模拟测试中,IO(默认) 可达 512K ops/s,远高于此值。这是因为真实 Redis IO 除了网络延迟外还包含连接池竞争、序列化开销等。使用纯 delay 的基准测试只能反映调度器本身的差异趋势,不能直接作为生产容量规划的绝对值。
实测验证(纯 delay 模拟,12核 JDK 21):场景1(IO=5ms, N=5,000)IO(默认) 512K vs 原生VT 393K,确认协程更优的趋势;场景2(IO=200ms, N=2,000)IO(默认) 23.6K vs 原生VT 24.2K,确认虚拟线程略优的趋势;场景3(IO=3000ms, N=1,000)二者均接近 330 ops/s(IO 耗时完全主导),虚拟线程在内存可控性上的优势成为关键区分点。
5. 结论
协程与虚拟线程并非对立关系,在真实系统中往往是混合共存的:CPU密集型计算、短IO快调用走 Dispatchers.Default 或 Dispatchers.IO,利用平台线程池的低调度开销;长阻塞IO、高并发外部调用走 Dispatchers.Virtual(需自己实现),利用虚拟线程的阻塞卸载能力;全链路统一在协程的结构化并发体系下管理,兼顾工程效率与资源效率。
理论公式帮我们理解底层资源边界,而「并发 × 时长」的联合判断,才是生产环境选型的核心依据。脱离并发谈IO时长、脱离时长谈并发规模,都无法得出准确的技术选型结论。同样地------实测新增的发现表明------脱离 CPU 占比谈调度器选型也会失之偏颇:当业务逻辑中 CPU 计算占比超过 20% 时,平台线程池的 work-stealing 机制往往比虚拟线程的阻塞卸载带来更大的吞吐量收益,这一维度值得在生产选型中额外关注。
虚拟线程适用于长 IO 大并发系统,但对于内存限制或短 IO 场景、以及 CPU 占比较高的混合负载场景,Kotlin IO 协程凭借轻量和高效调度更具优势。建议在关键系统中混合两种策略(如主逻辑用协程、外部接口调用用虚拟线程),以平衡响应时间与资源利用率。
附录 A:IO 密集型实测矩阵
环境:JDK 21.0.5 · Kotlin 2.3.21 · 12核CPU · BenchmarkVerification.kt
数值单位:吞吐量 ops/s
IO=1ms(极短IO):
| N | IO(默认) | IO(无限制) | VT-Dispatcher | 原生VT | 最优 |
|---|---|---|---|---|---|
| 500 | 122,730 | 39,258 | 34,274 | 35,345 | IO(默认) |
| 5,000 | 353,757 | 680,874 | 359,813 | 389,848 | IO(无限制) |
| 10,000 | 1,113,728 | 813,878 | 694,659 | 635,467 | IO(默认) |
IO=5ms(短IO):
| N | IO(默认) | IO(无限制) | VT-Dispatcher | 原生VT | 最优 |
|---|---|---|---|---|---|
| 500 | 36,312 | 54,195 | 33,680 | 35,347 | IO(无限制) |
| 5,000 | 512,290 | 366,806 | 369,156 | 392,856 | IO(默认) |
| 10,000 | 684,960 | 655,987 | 576,249 | 644,075 | IO(默认) |
IO=50ms(中IO):
| N | IO(默认) | IO(无限制) | VT-Dispatcher | 原生VT | 最优 |
|---|---|---|---|---|---|
| 5,000 | 76,010 | 76,230 | 83,636 | 86,060 | 原生VT |
| 10,000 | 129,186 | 137,151 | 169,742 | 174,732 | 原生VT |
IO=200ms(长IO):
| N | IO(默认) | IO(无限制) | VT-Dispatcher | 原生VT | 最优 |
|---|---|---|---|---|---|
| 5,000 | 23,569 | 24,248 | 23,917 | 24,161 | 原生VT |
| 10,000 | 43,771 | 43,672 | 46,961 | 48,172 | 原生VT |
IO=1000ms(超长IO): 所有调度器吞吐量趋同于 N/1.0sN / 1.0\text{s}N/1.0s。
附录 B:Dispatchers.IO 默认并行度限制的梯度影响
IO=5ms, N=5,000
| 并行度限制 | 64(默认) | 128 | 256 | 512 | 1024 | 无限制 |
|---|---|---|---|---|---|---|
| 吞吐量(ops/s) | 208,793 | 317,146 | 268,131 | 336,428 | 433,749 | 217,524 |
| 均延迟(ms) | 23.9 | 15.8 | 18.6 | 14.9 | 11.5 | 23.0 |
当 IO 延迟达到 200ms 时,所有并行度限制下的吞吐量趋同(约 22,000~23,000 ops/s)------IO 耗时完全主导,并行度限制不再产生影响。
附录 C:调度创建开销实测
| 操作 | 平均延迟 | 相对比 |
|---|---|---|
| 虚拟线程 submit+get | 18.2 μs | 0.6x |
| 虚拟线程 create+join | 22.0 μs | 0.8x |
| 平台线程池 submit+get | 22.0 μs | 0.8x |
| 协程 launch+join (Default) | 28.2 μs | 1.0x |
| 协程 delay(0)+IO 调度 | 38.0 μs | 1.3x |
| 平台线程 create+join | 225.7 μs | 8.0x |
附录 D:混合 CPU+IO 负载实测
| 场景 | CPU/IO 配比 | N | IO(默认) | IO(无限制) | 原生VT | 最优 |
|---|---|---|---|---|---|---|
| 轻量API | 2ms/5ms | 500 | 9,032 | 12,917 | 11,192 | IO(无限制) |
| 普通业务 | 10ms/20ms | 1,000 | 2,009 | 2,289 | 1,678 | IO(无限制) |
| 数据处理+外部调用 | 20ms/100ms | 2,000 | 1,011 | 1,347 | 1,019 | IO(无限制) |
| 复杂计算+慢IO | 50ms/500ms | 1,000 | 488 | 455 | 348 | IO(默认) |
| 报表生成+批量IO | 100ms/1000ms | 500 | 436 | 414 | 177 | IO(默认) |
附录 E:测试代码
kotlin
import kotlinx.coroutines.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicLong
import kotlin.math.sqrt
import kotlin.system.measureNanoTime
import kotlin.system.measureTimeMillis
/**
* 虚拟线程 vs 平台线程 vs 协程 全面性能对比基准测试
*
* 测试三个调度器:
* 1. Platform - 传统平台线程 (Thread)
* 2. Virtual - Java 虚拟线程 (Thread.ofVirtual)
* 3. IO - 协程 Dispatchers.IO (平台线程池)
* 4. IO(无限制) - 协程 Dispatchers.IO.limitedParallelism(Int.MAX_VALUE) 解除线程数限制
* 5. Virtual-Dispatcher - 协程 Dispatchers.Virtual (虚拟线程调度器)
*
* @author : zimo
* @date : 2026/06/24
*/
// ============================================================
// 数据结构
// ============================================================
data class PerfResult(
val testSuite: String,
val testName: String,
val dispatcher: String,
val concurrency: Int,
val workMs: Long,
val totalOps: Long,
val durationMs: Long,
val throughput: Double,
val avgLatencyMs: Double,
val p99LatencyMs: Double,
val memoryDeltaMB: Double,
)
val allResults = mutableListOf<PerfResult>()
// ============================================================
// 工具函数
// ============================================================
fun String.times(n: Int) = this.repeat(n)
/** 测量内存增量 */
inline fun <T> measureMemoryDelta(block: () -> T): Pair<T, Double> {
System.gc()
Thread.sleep(300)
val before = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
val result = block()
System.gc()
Thread.sleep(300)
val after = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
return result to ((after - before).toDouble() / (1024 * 1024))
}
/** CPU 密集型工作: 计算第 N 个素数 */
fun cpuWork(ms: Long) {
val start = System.nanoTime()
var n = 2L
while ((System.nanoTime() - start) / 1_000_000 < ms) {
var prime = true
val limit = sqrt(n.toDouble()).toLong() + 1
for (i in 2..limit) {
if (n % i == 0L) { prime = false; break }
}
@Suppress("UNUSED_EXPRESSION")
prime // busy work result discarded
n++
}
}
/** 获取当前活跃线程数估算 */
fun activeThreadEstimate(): Int = Thread.activeCount()
// ============================================================
// 测试 1: 纯 CPU 密集型
// ============================================================
suspend fun benchmarkCpuIntensive() {
println("=".times(60))
println("测试 1: 纯 CPU 密集型 (计算素数)")
println("=".times(60))
val cpuCores = Runtime.getRuntime().availableProcessors()
val workMs = 50L
val concurrencyLevels = listOf(cpuCores, cpuCores * 2, cpuCores * 4, cpuCores * 10, 200)
// CPU 密集型只用 Default / Virtual-Dispatcher / 原生 VT
// IO 调度器不适合 CPU 密集任务
val dispatchers = listOf(
"Default" to Dispatchers.Default,
)
val vtDispatcher = Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher()
println("%-8s | %-12s | %12s | %12s | %12s".format("并发", "调度器", "吞吐量ops/s", "平均延迟ms", "P99延迟ms"))
println("-".times(70))
for (n in concurrencyLevels) {
for ((name, dispatcher) in dispatchers) {
val result = runCoroutineCpuTest("CPU密集", "素数计算${workMs}ms", name, n, workMs, dispatcher)
allResults.add(result)
println("%-8d | %-12s | %12.0f | %12.1f | %12.1f".format(
n, name, result.throughput, result.avgLatencyMs, result.p99LatencyMs
))
}
// 原生虚拟线程 (非协程)
val vtResult = runNativeVTCpuTest("CPU密集", "素数计算${workMs}ms", n, workMs)
allResults.add(vtResult)
println("%-8d | %-12s | %12.0f | %12.1f | %12.1f".format(
n, "原生VT", vtResult.throughput, vtResult.avgLatencyMs, vtResult.p99LatencyMs
))
}
vtDispatcher.close()
println()
}
/** 协程 CPU 测试 */
suspend fun runCoroutineCpuTest(suite: String, name: String, dispatcherLabel: String, n: Int, workMs: Long, dispatcher: CoroutineDispatcher): PerfResult {
val latencies = ConcurrentLinkedQueue<Long>()
val ops = AtomicLong(0)
val rounds = 3
// 预热
coroutineScope { (1..n).map { launch(dispatcher) { cpuWork(workMs / 2) } }.joinAll() }
val (_, memDelta) = measureMemoryDelta {
runBlocking {
val duration = measureTimeMillis {
repeat(rounds) {
coroutineScope {
(1..n).map {
launch(dispatcher) {
val t0 = System.nanoTime()
cpuWork(workMs)
latencies.add((System.nanoTime() - t0) / 1_000_000)
ops.incrementAndGet()
}
}.joinAll()
}
}
}
}
}
val sorted = latencies.toList().sorted()
val p99 = if (sorted.isNotEmpty()) sorted[(sorted.size * 0.99).toInt().coerceAtMost(sorted.size - 1)].toDouble() else 0.0
val avg = latencies.toList().average()
return PerfResult(suite, name, dispatcherLabel, n, workMs, ops.get(), 0, ops.get().toDouble(), avg, p99, memDelta)
}
/** 原生虚拟线程 CPU 测试 */
fun runNativeVTCpuTest(suite: String, name: String, n: Int, workMs: Long): PerfResult {
val latencies = ConcurrentLinkedQueue<Long>()
val ops = AtomicLong(0)
val rounds = 3
val executor = Executors.newVirtualThreadPerTaskExecutor()
// 预热
(1..n).map { executor.submit { cpuWork(workMs / 2) } }.forEach { it.get() }
val (_, memDelta) = measureMemoryDelta {
val duration = measureTimeMillis {
repeat(rounds) {
val futures = (1..n).map {
executor.submit {
val t0 = System.nanoTime()
cpuWork(workMs)
latencies.add((System.nanoTime() - t0) / 1_000_000)
ops.incrementAndGet()
}
}
futures.forEach { it.get() }
}
}
}
executor.close()
val sorted = latencies.toList().sorted()
val p99 = if (sorted.isNotEmpty()) sorted[(sorted.size * 0.99).toInt().coerceAtMost(sorted.size - 1)].toDouble() else 0.0
val avg = latencies.toList().average()
return PerfResult(suite, name, "原生VT", n, workMs, ops.get(), 0, ops.get().toDouble(), avg, p99, memDelta)
}
// ============================================================
// 测试 2: 纯 IO 密集型 (delay 模拟)
// ============================================================
suspend fun benchmarkIOIntensive() {
println("=".times(60))
println("测试 2: 纯 IO 密集型 (delay 模拟阻塞)")
println("=".times(60))
// IO 时长 × 并发 矩阵测试
val ioDelays = listOf(1L, 5L, 50L, 200L, 1000L) // ms
val concurrencyLevels = listOf(100, 500, 1000, 5000, 10000)
// 调度器: IO(默认), IO(无限制), Virtual-Dispatcher, 原生VT
println()
println("=== 矩阵测试: 不同 IO 时长 × 不同并发 ===")
for (delayMs in ioDelays) {
println("\n--- IO延迟 = ${delayMs}ms ---")
println("%-8s | %-16s | %12s | %12s | %18s".format("N", "调度器", "吞吐量ops/s", "均延迟ms", "内存增量MB"))
println("-".times(80))
for (n in concurrencyLevels) {
// Dispatchers.IO (默认)
val rIO = runIOTest("IO密集", "delay=${delayMs}ms", "IO(默认)", n, delayMs, Dispatchers.IO)
allResults.add(rIO)
println("%-8d | %-16s | %12.0f | %12.1f | %18.2f".format(n, "IO(默认)", rIO.throughput, rIO.avgLatencyMs, rIO.memoryDeltaMB))
// Dispatchers.IO (无限制)
val ioUnlimited = Dispatchers.IO.limitedParallelism(Int.MAX_VALUE)
val rIOU = runIOTest("IO密集", "delay=${delayMs}ms", "IO(无限制)", n, delayMs, ioUnlimited)
allResults.add(rIOU)
println("%-8d | %-16s | %12.0f | %12.1f | %18.2f".format(n, "IO(无限制)", rIOU.throughput, rIOU.avgLatencyMs, rIOU.memoryDeltaMB))
// 跳过小并发的 VT (没意义)
if (n >= 500) {
val vtDispatcher = Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher()
val rVD = runIOTest("IO密集", "delay=${delayMs}ms", "VT-Dispatcher", n, delayMs, vtDispatcher)
allResults.add(rVD)
println("%-8d | %-16s | %12.0f | %12.1f | %18.2f".format(n, "VT-Dispatcher", rVD.throughput, rVD.avgLatencyMs, rVD.memoryDeltaMB))
vtDispatcher.close()
val rVT = runNativeVTTest("IO密集", "delay=${delayMs}ms", n, delayMs)
allResults.add(rVT)
println("%-8d | %-16s | %12.0f | %12.1f | %18.2f".format(n, "原生VT", rVT.throughput, rVT.avgLatencyMs, rVT.memoryDeltaMB))
}
}
}
println()
}
/** 协程 IO 测试 */
suspend fun runIOTest(suite: String, name: String, dispLabel: String, n: Int, delayMs: Long, dispatcher: CoroutineDispatcher): PerfResult {
val latencies = ConcurrentLinkedQueue<Long>()
val rounds = if (n > 2000) 2 else 5
var totalOps = 0L
// 预热
coroutineScope { (1..n).map { launch(dispatcher) { delay(delayMs) } }.joinAll() }
val (_, memDelta) = measureMemoryDelta {
runBlocking {
repeat(rounds) {
coroutineScope {
val jobs = (1..n).map {
launch(dispatcher) {
val t0 = System.nanoTime()
delay(delayMs)
latencies.add((System.nanoTime() - t0) / 1_000_000)
}
}
totalOps += jobs.size
jobs.joinAll()
}
}
}
}
val sorted = latencies.toList().sorted()
val p99 = if (sorted.isNotEmpty()) sorted[(sorted.size * 0.99).toInt().coerceAtMost(sorted.size - 1)].toDouble() else 0.0
val avg = latencies.toList().average()
val throughput = if (avg > 0) 1000.0 / (avg / rounds) * n else 0.0
// 更好的吞吐量计算: 在理想情况下,吞吐量 = N / (IO延迟) × (调度器并行度系数)
// 我们用实际: ops / (预估总耗时) 估算
val estimatedThroughput = if (sorted.isNotEmpty()) {
totalOps * 1000.0 / (sorted.sum() / sorted.size * rounds)
} else 0.0
// 简化: 吞吐量 = 并发数 / (延迟/rounds) = n * rounds / avg_latency_in_seconds
val estimatedDurationPerRound = sorted.map { it.toDouble() }.average()
val simpleThroughput = if (estimatedDurationPerRound > 0) n * 1000.0 / estimatedDurationPerRound else 0.0
return PerfResult(suite, name, dispLabel, n, delayMs, totalOps, 0, simpleThroughput, avg, p99, memDelta)
}
/** 原生虚拟线程 IO 测试 */
fun runNativeVTTest(suite: String, name: String, n: Int, delayMs: Long): PerfResult {
val latencies = ConcurrentLinkedQueue<Long>()
val rounds = if (n > 2000) 2 else 5
var totalOps = 0L
val executor = Executors.newVirtualThreadPerTaskExecutor()
// 预热
(1..n).map { executor.submit { Thread.sleep(delayMs) } }.forEach { it.get() }
val (_, memDelta) = measureMemoryDelta {
repeat(rounds) {
val futures = (1..n).map {
executor.submit {
val t0 = System.nanoTime()
Thread.sleep(delayMs)
latencies.add((System.nanoTime() - t0) / 1_000_000)
}
}
totalOps += futures.size
futures.forEach { it.get() }
}
}
executor.close()
val sorted = latencies.toList().sorted()
val p99 = if (sorted.isNotEmpty()) sorted[(sorted.size * 0.99).toInt().coerceAtMost(sorted.size - 1)].toDouble() else 0.0
val avg = latencies.toList().average()
val simpleThroughput = if (avg > 0) n * 1000.0 / avg else 0.0
return PerfResult(suite, name, "原生VT", n, delayMs, totalOps, 0, simpleThroughput, avg, p99, memDelta)
}
// ============================================================
// 测试 3: 混合 CPU + IO (贴近真实业务)
// ============================================================
suspend fun benchmarkMixed() {
println("=".times(60))
println("测试 3: 混合 CPU+IO (模拟真实业务)")
println("=".times(60))
// 场景: 50% CPU计算 + 50% IO等待 (如: 处理请求 + 调外部服务 + 返回结果)
data class MixedScenario(val label: String, val cpuMs: Long, val ioMs: Long, val concurrency: Int)
val scenarios = listOf(
MixedScenario("轻量API", 2, 5, 500),
MixedScenario("普通业务", 10, 20, 1000),
MixedScenario("数据处理+外部调用", 20, 100, 2000),
MixedScenario("复杂计算+慢IO", 50, 500, 1000),
MixedScenario("报表生成+批量IO", 100, 1000, 500),
)
println()
println("%-20s | %5s/%5s | %6s | %-16s | %10s | %10s | %10s".format(
"场景", "CPUms", "IOms", "N", "调度器", "吞吐量", "均延迟ms", "内存MB"
))
println("-".times(100))
for (scenario in scenarios) {
val cpuMs = scenario.cpuMs
val ioMs = scenario.ioMs
val n = scenario.concurrency
// IO (默认)
val r1 = runMixedTest("混合负载", scenario.label, "IO(默认)", n, cpuMs, ioMs, Dispatchers.IO)
allResults.add(r1)
println("%-20s | %5d/%5d | %6d | %-16s | %10.0f | %10.1f | %10.2f".format(
scenario.label, cpuMs, ioMs, n, "IO(默认)", r1.throughput, r1.avgLatencyMs, r1.memoryDeltaMB
))
// IO (无限制)
val ioUnlimited = Dispatchers.IO.limitedParallelism(Int.MAX_VALUE)
val r2 = runMixedTest("混合负载", scenario.label, "IO(无限制)", n, cpuMs, ioMs, ioUnlimited)
allResults.add(r2)
println("%-20s | %5d/%5d | %6d | %-16s | %10.0f | %10.1f | %10.2f".format(
scenario.label, cpuMs, ioMs, n, "IO(无限制)", r2.throughput, r2.avgLatencyMs, r2.memoryDeltaMB
))
// VT-Dispatcher
val vtDispatcher = Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher()
val r3 = runMixedTest("混合负载", scenario.label, "VT-Dispatcher", n, cpuMs, ioMs, vtDispatcher)
allResults.add(r3)
println("%-20s | %5d/%5d | %6d | %-16s | %10.0f | %10.1f | %10.2f".format(
scenario.label, cpuMs, ioMs, n, "VT-Dispatcher", r3.throughput, r3.avgLatencyMs, r3.memoryDeltaMB
))
vtDispatcher.close()
// 原生 VT
val r4 = runNativeVTMixedTest("混合负载", scenario.label, n, cpuMs, ioMs)
allResults.add(r4)
println("%-20s | %5d/%5d | %6d | %-16s | %10.0f | %10.1f | %10.2f".format(
scenario.label, cpuMs, ioMs, n, "原生VT", r4.throughput, r4.avgLatencyMs, r4.memoryDeltaMB
))
}
println()
}
/** 协程混合负载测试: CPU计算 + delay(IO) 交替 */
suspend fun runMixedTest(suite: String, name: String, dispLabel: String, n: Int, cpuMs: Long, ioMs: Long, dispatcher: CoroutineDispatcher): PerfResult {
val latencies = ConcurrentLinkedQueue<Long>()
val rounds = if (n > 1000) 3 else 5
var totalOps = 0L
// 预热
coroutineScope {
(1..n).map {
launch(dispatcher) {
cpuWork(cpuMs / 2)
delay(ioMs / 2)
}
}.joinAll()
}
val (_, memDelta) = measureMemoryDelta {
runBlocking {
repeat(rounds) {
coroutineScope {
val jobs = (1..n).map {
launch(dispatcher) {
val t0 = System.nanoTime()
// 混合工作: CPU计算 → IO等待 → CPU计算
cpuWork(cpuMs)
delay(ioMs)
cpuWork(cpuMs / 3)
latencies.add((System.nanoTime() - t0) / 1_000_000)
}
}
totalOps += jobs.size
jobs.joinAll()
}
}
}
}
val sorted = latencies.toList().sorted()
val p99 = if (sorted.isNotEmpty()) sorted[(sorted.size * 0.99).toInt().coerceAtMost(sorted.size - 1)].toDouble() else 0.0
val avg = latencies.toList().average()
val throughput = if (avg > 0) n * 1000.0 / avg else 0.0
return PerfResult(suite, name, dispLabel, n, cpuMs + ioMs, totalOps, 0, throughput, avg, p99, memDelta)
}
/** 原生虚拟线程混合负载测试 */
fun runNativeVTMixedTest(suite: String, name: String, n: Int, cpuMs: Long, ioMs: Long): PerfResult {
val latencies = ConcurrentLinkedQueue<Long>()
val rounds = if (n > 1000) 3 else 5
var totalOps = 0L
val executor = Executors.newVirtualThreadPerTaskExecutor()
// 预热
(1..n).map {
executor.submit {
cpuWork(cpuMs / 2)
Thread.sleep(ioMs / 2)
}
}.forEach { it.get() }
val (_, memDelta) = measureMemoryDelta {
repeat(rounds) {
val futures = (1..n).map {
executor.submit {
val t0 = System.nanoTime()
cpuWork(cpuMs)
Thread.sleep(ioMs)
cpuWork(cpuMs / 3)
latencies.add((System.nanoTime() - t0) / 1_000_000)
}
}
totalOps += futures.size
futures.forEach { it.get() }
}
}
executor.close()
val sorted = latencies.toList().sorted()
val p99 = if (sorted.isNotEmpty()) sorted[(sorted.size * 0.99).toInt().coerceAtMost(sorted.size - 1)].toDouble() else 0.0
val avg = latencies.toList().average()
val throughput = if (avg > 0) n * 1000.0 / avg else 0.0
return PerfResult(suite, name, "原生VT", n, cpuMs + ioMs, totalOps, 0, throughput, avg, p99, memDelta)
}
// ============================================================
// 测试 4: 内存开销对比
// ============================================================
suspend fun benchmarkMemoryOverhead() {
println("=".times(60))
println("测试 4: 内存开销对比 (大量并发任务)")
println("=".times(60))
val levels = listOf(100, 1000, 5000, 10000)
println("%-8s | %-16s | %18s | %18s".format("任务数", "调度器", "内存增量MB", "单任务估算KB"))
println("-".times(70))
for (count in levels) {
// 协程 IO
val (_, coMem) = measureMemoryDelta {
runBlocking {
coroutineScope {
(1..count).map { launch(Dispatchers.IO) { delay(1) } }.joinAll()
}
}
}
println("%-8d | %-16s | %18.2f | %18.2f".format(count, "IO协程", coMem, coMem * 1024 / count))
// 协程 Default
val (_, defMem) = measureMemoryDelta {
runBlocking {
coroutineScope {
(1..count).map { launch(Dispatchers.Default) { delay(1) } }.joinAll()
}
}
}
println("%-8d | %-16s | %18.2f | %18.2f".format(count, "Default协程", defMem, defMem * 1024 / count))
// 虚拟线程 (原生)
val (_, vtMem) = measureMemoryDelta {
val executor = Executors.newVirtualThreadPerTaskExecutor()
val futures = (1..count).map { executor.submit { Thread.sleep(1) } }
futures.forEach { it.get() }
executor.close()
}
println("%-8d | %-16s | %18.2f | %18.2f".format(count, "虚拟线程", vtMem, vtMem * 1024 / count))
// 平台线程 (少量,因为开销大)
if (count <= 1000) {
val (_, ptMem) = measureMemoryDelta {
val executor = Executors.newCachedThreadPool()
val futures = (1..count).map { executor.submit { Thread.sleep(1) } }
futures.forEach { it.get() }
executor.shutdown()
}
println("%-8d | %-16s | %18.2f | %18.2f".format(count, "平台线程池", ptMem, ptMem * 1024 / count))
} else {
println("%-8d | %-16s | %18s | %18s".format(count, "平台线程池", "(跳过-资源过大)", "-"))
}
}
println()
}
// ============================================================
// 测试 5: 调度/创建开销
// ============================================================
fun benchmarkSchedulingOverhead() {
println("=".times(60))
println("测试 5: 任务创建与调度开销")
println("=".times(60))
val iterations = 10_000
val warmup = 1000
data class OverheadResult(val label: String, val totalNs: Long, val count: Int) {
val avgNs: Double get() = totalNs.toDouble() / count
}
val results = mutableListOf<OverheadResult>()
// 1. 协程 launch + join (最小开销)
runBlocking {
repeat(warmup) { launch(Dispatchers.Default) { }.join() }
val t = measureNanoTime {
repeat(iterations) { launch(Dispatchers.Default) { }.join() }
}
results.add(OverheadResult("协程launch+join(Default)", t, iterations))
}
// 2. 协程 launch + delay(0) + join
runBlocking {
repeat(warmup / 10) { launch(Dispatchers.IO) { delay(0) }.join() }
val t = measureNanoTime {
repeat(iterations / 10) { launch(Dispatchers.IO) { delay(0) }.join() }
}
results.add(OverheadResult("协程delay(0)+IO调度", t, iterations / 10))
}
// 3. 原生虚拟线程 create+start+join
repeat(warmup) { Thread.ofVirtual().start { }.join() }
val vtCreate = measureNanoTime {
repeat(iterations) { Thread.ofVirtual().start { }.join() }
}
results.add(OverheadResult("虚拟线程create+join", vtCreate, iterations))
// 4. 虚拟线程 Executor submit+get
val vtExecutor = Executors.newVirtualThreadPerTaskExecutor()
repeat(warmup) { vtExecutor.submit { }.get() }
val vtSubmit = measureNanoTime {
repeat(iterations) { vtExecutor.submit { }.get() }
}
results.add(OverheadResult("虚拟线程submit+get", vtSubmit, iterations))
vtExecutor.close()
// 5. 平台线程 create+start+join
repeat(warmup / 100) { val t = Thread { }; t.start(); t.join() }
val ptCreate = measureNanoTime {
repeat(iterations / 100) { val t = Thread { }; t.start(); t.join() }
}
results.add(OverheadResult("平台线程create+join", ptCreate, iterations / 100))
// 6. 平台线程池 submit+get
val ptExecutor = Executors.newCachedThreadPool()
repeat(warmup / 10) { ptExecutor.submit { }.get() }
val ptSubmit = measureNanoTime {
repeat(iterations / 10) { ptExecutor.submit { }.get() }
}
results.add(OverheadResult("平台线程池submit+get", ptSubmit, iterations / 10))
ptExecutor.shutdown()
println("%-30s | %10s | %12s".format("操作", "平均延迟", "相对比"))
println("-".times(60))
val baseline = results.first().avgNs
for (r in results) {
val ratio = r.avgNs / baseline
val unit = if (r.avgNs > 10_000) "μs" else "ns"
val display = if (r.avgNs > 10_000) r.avgNs / 1000 else r.avgNs
println("%-30s | %8.1f %-3s | %10.1fx".format(r.label, display, unit, ratio))
}
println()
}
// ============================================================
// 测试 6: 线程池并发限制影响分析
// ============================================================
suspend fun benchmarkThreadPoolLimits() {
println("=".times(60))
println("测试 6: Dispatchers.IO 线程池限制影响分析")
println("=".times(60))
println(" 说明: Dispatchers.IO 默认并行度 = max(64, CPU核数)")
println(" 本测试对比不同并行度限制下的表现")
println()
val ioDelays = listOf(5L, 50L, 200L) // ms
val concurrency = 5000
val limits = listOf(64, 128, 256, 512, 1024, Int.MAX_VALUE)
println("%-6s | %-12s | %12s | %12s | %12s".format("IOms", "并行度限制", "吞吐量ops/s", "均延迟ms", "P99延迟ms"))
println("-".times(70))
for (delayMs in ioDelays) {
for (limit in limits) {
val dispatcher = Dispatchers.IO.limitedParallelism(limit)
val label = if (limit == Int.MAX_VALUE) "无限制" else "$limit"
val latencies = ConcurrentLinkedQueue<Long>()
// 预热
coroutineScope { (1..concurrency).map { launch(dispatcher) { delay(delayMs) } }.joinAll() }
coroutineScope {
val jobs = (1..concurrency).map {
launch(dispatcher) {
val t0 = System.nanoTime()
delay(delayMs)
latencies.add((System.nanoTime() - t0) / 1_000_000)
}
}
jobs.joinAll()
}
val sorted = latencies.toList().sorted()
val p99 = if (sorted.isNotEmpty()) sorted[(sorted.size * 0.99).toInt().coerceAtMost(sorted.size - 1)].toDouble() else 0.0
val avg = latencies.toList().average()
val throughput = if (avg > 0) concurrency * 1000.0 / avg else 0.0
println("%-6d | %-12s | %12.0f | %12.1f | %12.1f".format(delayMs, label, throughput, avg, p99))
}
println()
}
println()
}
// ============================================================
// 生成测试报告
// ============================================================
fun generateReport() {
val sep = "=".times(60)
println(sep)
println("测试报告")
println(sep)
// === 按测试套汇总 ===
val suites = allResults.groupBy { it.testSuite }
for ((suite, results) in suites) {
println("\n## $suite")
println()
val byDispatcher = results.groupBy { it.dispatcher }
val dispatchers = byDispatcher.keys.sorted()
// 表头
print("%-20s".format("测试"))
for (d in dispatchers) {
print(" | %12s".format(d.take(12)))
}
println()
print("-".times(20))
for (d in dispatchers) print(" | ${"-".times(12)}")
println()
val byTest = results.groupBy { "${it.concurrency}@${it.workMs}ms" to it.testName }
for ((key, group) in byTest) {
print("%-20s".format(group.first().testName.take(20)))
for (d in dispatchers) {
val r = group.find { it.dispatcher == d }
if (r != null) {
print(" | %12.0f".format(r.throughput))
} else {
print(" | %12s".format("-"))
}
}
println()
}
}
// === 综合排名 ===
println("\n## 综合排名 (按场景-吞吐量)")
println()
val ranked = allResults.sortedByDescending { it.throughput }
println("%-4s | %-20s | %-16s | %8s | %8s | %10s | %10s".format(
"排名", "场景", "调度器", "并发", "工作ms", "吞吐量", "延迟ms"
))
println("-".times(85))
ranked.take(30).forEachIndexed { idx, r ->
println("%-4d | %-20s | %-16s | %8d | %8d | %10.0f | %10.1f".format(
idx + 1, r.testName.take(20), r.dispatcher, r.concurrency, r.workMs, r.throughput, r.avgLatencyMs
))
}
// === 调度器全局对比 ===
println("\n## 调度器全局对比 (所有场景的调和均值)")
println()
val globalByDispatcher = allResults.groupBy { it.dispatcher }.mapValues { (_, list) ->
val valid = list.filter { it.throughput > 0 }
if (valid.isNotEmpty()) valid.map { it.throughput }.average() else 0.0
}
globalByDispatcher.entries.sortedByDescending { it.value }.forEach { (disp, avg) ->
println(" $disp: 平均吞吐量 ${"%.0f".format(avg)} ops/s")
}
}
// ============================================================
// 主入口
// ============================================================
fun main() = runBlocking {
val cpuCores = Runtime.getRuntime().availableProcessors()
val javaVersion = System.getProperty("java.version")
println("╔══════════════════════════════════════════════════════════════╗")
println("║ 虚拟线程 / 协程 / 平台线程 全面性能对比基准测试 ║")
println("║ JDK: $javaVersion | CPU: ${cpuCores}核 | OS: ${System.getProperty("os.name")} ║")
println("╚══════════════════════════════════════════════════════════════╝")
println()
println("测试调度器:")
println(" - IO(默认): Dispatchers.IO (max(64, CPU核)并行)")
println(" - IO(无限制): Dispatchers.IO.limitedParallelism(MAX)")
println(" - Default: Dispatchers.Default (CPU核数个线程)")
println(" - VT-Dispatcher: Dispatchers.Virtual (虚拟线程调度器)")
println(" - 原生VT: Thread.ofVirtual() (非协程)")
println(" - 平台线程: 传统 Thread / CachedThreadPool")
println()
try {
benchmarkSchedulingOverhead()
benchmarkMemoryOverhead()
benchmarkThreadPoolLimits()
benchmarkCpuIntensive()
benchmarkIOIntensive()
benchmarkMixed()
// 生成测试报告
generateReport()
} catch (e: Exception) {
println("测试异常: ${e.message}")
e.printStackTrace()
}
}
- 工具方法
kotlin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
/**
* 虚拟线程与协程工具类
* 提供虚拟线程创建、平台线程创建、以及虚拟线程调度器扩展
*
* @author : zimo
* @date : 2026/06/24
*/
/**
* 使用虚拟线程执行回调
* 注意: 虚拟线程在阻塞时自动卸载载体线程,适合长IO高并发场景
*/
fun virtual(callback: () -> Unit): Unit {
Thread.ofVirtual().start(callback)
}
/**
* 使用平台线程执行回调
* 注意: 平台线程阻塞时会占用内核资源,不适合大量阻塞场景
*/
fun thread(callback: () -> Unit): Unit {
Thread(callback).start()
}
/**
* 虚拟线程调度器扩展属性
* 将虚拟线程 Executor 转换为协程调度器,实现协程体系下的虚拟线程调度
*/
val Dispatchers.Virtual get() = Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher()