Skip to content

Renaissance5项劣化分析 #47

Description

@lfeng14

下面统一按“C2 性能 = 100%,数值越高越好”计算。之前比例由 dev_4 的耗时比取倒数,当前比例采用你在另一环境的实测结果。

总体对比

Renaissance 子项 dev_4 之前性能 当前性能 相对旧 C3 提升 当前 C3/C2 耗时 当前状态
neo4j-analytics 16.8% 32% 1.90x 3.13x 仍严重劣化
page-rank 31.6% 46% 1.46x 2.17x 仍明显劣化
par-mnemonics 31.3% 97% 3.10x 1.03x 基本追平
philosophers 36.1% 116% 3.21x 0.86x C3 反超约 16%
reactors 60.2% 90% 1.50x 1.11x 接近追平

“当前 C3/C2 耗时”按 100 / 性能比例 换算;例如 32% 表示 C3 耗时约为 C2 的 3.13 倍。

功能变化与残余缺口

子项 当前分支新增、且与该项相关的能力 旧分析中对应的作用 当前仍待补齐 更新后的性能结论
neo4j-analytics Unsafe _getByte_getShort_getLong_getIntVolatile_getLongVolatile_putLongVolatileClass.isInstanceSystem.arraycopy 已覆盖旧版 8 个主要 Unsafe 缺口中的 6 个。按旧 profile 计算,大约消掉 21.78 个百分点的可辨认 Unsafe cycles;同时消除部分 JNI/native transition _getInt_putByte_allocateInstance;完整 EA/标量替换;一般代码质量 从 16.8% 提升到 32%,说明补齐的 Unsafe/type/arraycopy 确实有效。但 _getInt 在旧 profile 中单独占约 10.19% cycles,仍是最明确主因
page-rank Unsafe _getLong_getShort_putLong_getByte_putInt_getReference_getReferenceVolatilehashCode/identityHashCodeisInstance/isArray/isPrimitive/isAssignableFromgetLengtharraycopy 旧版 12 个普通 Unsafe 缺口中补了 7 个;hash/type/array 次级组除 allocation 外基本补齐。按旧 profile 约消掉 17.96 个百分点的已知 fallback cycles _getInt_putShort_putByte_getDouble_putDouble_allocateInstance;EA;Spark/TimSort/Kryo 热点代码膨胀和内联边界 从 31.6% 提升到 46%,符合“修了一半显式 fallback、仍剩一半”的静态判断。当前主要问题仍是 Kryo/LZ4 普通 Unsafe 访问
par-mnemonics arraycopyClass.isPrimitivevectorizedHashCode;StringBuilder 构造器 intrinsic;StringBuilder/StringBuffer concat fusion;部分字符串编码 intrinsic 旧 C2 A/B 中,单独禁用 arraycopy 可解释总差距约 29.7%;vectorizedHashCode/isPrimitive 是次要项;concat fusion 有助于短字符串构造 _allocateInstance;完整 EA;部分 stream/lambda 对象消除;一般代码尺寸和内联反馈 从 31.3% 提升到 97%,基本证明 arraycopy 是旧回归的核心开关之一。allocateInstance 虽未实现,但已经不足以造成明显总分回归
philosophers Class.isInstancecompareAndSetReferencegetReferenceVolatile;其他 reference compare-and-exchange/weak-CAS;更多 VarHandle/Unsafe 内存序 lowering 正好覆盖旧版两个最大因果组:isInstance 单项约对应旧总差距 30.6%;reference CAS + volatile load 单项约对应 22.6%。二者在 STM 中会被冲突/重试进一步放大 _allocateInstance;完整 EA;profile devirtualization 的收益模型;并发 STM 下的代码形态和多模态稳定性;primitive getAndAdd direct intrinsic 从 36.1% 提升到 116%,旧版 intrinsic 根因已经基本消失。新结果还表明 intrinsic 补齐改善了事务时序,收益远大于 flat profile 占比
reactors compareAndSetReferencecompareAndExchangeLongputInt/getIntVolatile/putReferenceVolatileisInstance/isArrayarraycopy;weak reference CAS 旧版这些 fallback 的可见上限只有约 8%–10% cycles,但它们位于 ForkJoin、队列和消息发送链,修复后可能通过内联及调度反馈产生放大收益 普通 _getInt_allocateInstance;恢复 direct primitive getAndAdd{Byte,Short,Int,Long};跨 reactor 调度链代码膨胀;EA/对象消除 从 60.2% 提升到 90%,改善比旧 A/B 预期更大,说明并发调度反馈明显。剩余约 10% 更可能来自 getAndAdd 退化和整体代码形态

当前仍缺失的 intrinsic 汇总

待补功能 影响子项 旧证据/预期影响 优先级
_getInt neo4j、page-rank、reactors Neo4j 旧 profile 中 Java wrapper + VM helper 合计约 10.19%;PageRank 约 8.52% P0
_putByte neo4j、page-rank Neo4j 约 2.20%;PageRank 约 1.79% P0
_putShort page-rank PageRank 约 2.20% P0
_getDouble/_putDouble page-rank 合计约 0.73%,自身不大,但会影响 Kryo 内联和 transition P1
_allocateInstance 五项均可能命中,尤其 page-rank/par-mnemonics 旧实验:PageRank 约解释总差距 10.2%;par-mnemonics 约 21.6%;其他项较低 P0/P1
primitive _getAndAdd{Byte,Short,Int,Long} direct intrinsic reactors 为主,其他并发 workload dev_4 有、当前分支反而没有;目前会执行 Java volatile-load + weak-CAS 循环 P1
Java heap EA / scalar replacement page-rank、par-mnemonics、reactors 当前 DoEscapeAnalysis 主要只是内联启发式,没有 C2 ConnectionGraph 对等实现 P1
profile devirtualization 收益模型 philosophers、Scala/actor workload transformation 本身与旧分支相同,但 intrinsic 补齐后总体结果已改变;需要重新 A/B P2
通用代码尺寸、内联边界 page-rank、reactors 旧版热点方法存在 1.4–3.3x 代码膨胀;reactors 的消息链仍是主要残余候选 P2

当前分支其他可能产生收益的公共变化

这些功能已经进入当前代码,但无法仅凭现有跨环境数据准确拆分贡献:

  • 默认开启 counted-loop safepoint strip mining,chunk 默认 1000:jeandle_globals.hpp
  • 新增 arraycopy lowering 和 specialization:jeandleIntrinsicLowering.cpp
  • 新增 StringBuilder concat fusion:jeandleStringConcat.cpp
  • 补齐 hash、Class query、reference CAS、大量 Unsafe volatile/acquire/release/opaque 操作:jeandleIntrinsicLowering.cpp
  • 增加 fresh-array length folding、CHA/MethodHandle 支持、receiver type 保留和更多循环 canonicalization。
  • 当前仍没有已接入 pipeline 的完整 allocation-elimination/escape-analysis pass;现有 DoEscapeAnalysis 使用点仍主要是内联启发式:jeandleCompilation.cpp

综合看,接下来最划算的实现顺序是:

  1. _getInt_putByte_putShort
  2. _allocateInstance
  3. _getDouble/_putDouble
  4. 恢复 primitive direct getAndAdd,重点验证 reactors。
  5. 再处理 EA、代码尺寸和内联质量。

修完前两批后,Neo4j 和 PageRank 应当是最值得立即重测的两个验收项。


初测完成:这 5 项 C3 全部显著慢于 C2,几何平均耗时达到 C2 的 3.08 倍

Benchmark C2 末5轮均值 (ms) C3 末5轮均值 (ms) C3/C2 (倍) C3 回退幅度
neo4j-analytics 1,566.8 9,322.6 5.95 +495.0%
page-rank 3,242.0 10,255.9 3.16 +216.3%
par-mnemonics 3,961.1 12,673.0 3.20 +219.9%
philosophers 6,244.4 17,282.2 2.77 +176.8%
reactors 20,388.4 33,879.6 1.66 +66.2%

测试口径:

  • BiSheng JDK 21.0.9 release build
  • C2:-XX:-TieredCompilation -XX:-UseJeandleCompiler
  • C3:-XX:-TieredCompilation -XX:+UseJeandleCompiler
  • NUMA node 2
  • 每项 10 轮,统计末 5 轮
  • 共 100 个 iteration,全部成功,无异常标记
  • 末 5 轮 CV 为 0.07%–6.63%,差距远大于运行波动

初步建议:

  1. 先分析 par-mnemonics:3.2× 回退且方差最低,最适合做 C2/C3 perf、热点汇编和编译日志对照。
  2. 随后分析 neo4j-analytics:回退最严重,达到 5.95×。
  3. 再用 page-rank 判断问题是否也集中在 Spark closure、集合和装箱路径。
  4. philosophersreactors 涉及并发和调度,适合在较窄案例确认问题后分析。

详细评估见 [assessment.md](/home/luofeng/jdk21-c3-jit/results/renaissance-c2-c3-selected-20260731_1036/assessment.md),结构化数据见 [comparison-last-5.tsv](/home/luofeng/jdk21-c3-jit/results/renaissance-c2-c3-selected-20260731_1036/comparison-last-5.tsv)。原始日志:[C2](/home/luofeng/jdk21-c3-jit/results/renaissance-c2-c3-selected-20260731_1036/c2/process.log)、[C3](/home/luofeng/jdk21-c3-jit/results/renaissance-c2-c3-selected-20260731_1036/c3/process.log)。


结论:neo4j-analytics 的主要劣化原因是 C3 缺失 Neo4j 高频依赖的 HotSpot intrinsic,尤其是 Unsafe 普通及 volatile 内存访问。结论置信度较高,源码、CPU profile 和因果 A/B 实验三者相互吻合。

实验 稳态时间
C2 1.618 s
C3 9.213 s
C2 禁用 Unsafe intrinsic 5.996 s
C2 禁用全部已识别缺失 intrinsic 6.976 s

在 C2 中主动禁用 C3 缺失的 intrinsic 后,复现了约 70.5% 的 C3/C2 总差距。

C3 当前缺失的关键功能:

  • Unsafe 普通/volatile load/store:
    _getInt_getByte_getLong_getShort_getIntVolatile_getLongVolatile_putByte_putLongVolatile
  • _isInstance
  • _arraycopy
  • _allocateInstance

C3 的 Unsafe 白名单目前基本只有 getAndAddcompareAndSet 原子族,[C3 支持表 (line 163)](/home/luofeng/jdk21-c3-jit/src/hotspot/share/jeandle/jeandleIntrinsicLowering.cpp:163);而 C2 明确支持完整普通/volatile/acquire/opaque/unaligned 访问族,以及 [`arraycopy` (line 578)](/home/luofeng/jdk21-c3-jit/src/hotspot/share/opto/c2compiler.cpp:578)、[`allocateInstance/isInstance` (line 687)](/home/luofeng/jdk21-c3-jit/src/hotspot/share/opto/c2compiler.cpp:687)。

CPU profile 也直接反映了 fallback:

  • ThreadStateTransition::transition_from_native:16.44%
  • Unsafe.getInt + Unsafe_GetInt:10.19%
  • Unsafe.getByte + Unsafe_GetByte:8.03%
  • Unsafe.getLongVolatile + Unsafe_GetLongVolatile:6.10%
  • 可识别的 Unsafe wrapper/runtime 合计约 34%

因此 C3 原本应该生成直接 load/store 的位置,实际频繁进入 Unsafe_Get*/Unsafe_Put* VM native 入口,并承担 Java/native 状态切换成本。[C3 原始热点 (line 1)](/home/luofeng/jdk21-c3-jit/results/neo4j-c2-c3-analysis-20260731/c3-profile-v2/perf-report-fields.txt:1)

硬件计数器进一步排除了“缓存劣化”为主因:

  • 指令数:C3 是 C2 的 2.71 倍
  • IPC:2.52 → 1.59,下降 36.9%
  • cycles:4.30 倍
  • cache-miss rate:0.944% → 0.802%,反而降低

所以本质是执行了更多 fallback 指令,并且 native 边界降低了流水线效率。

修复优先级建议:

  1. 先补齐 Unsafe 普通/volatile load/store lowering。
  2. 再补 _isInstance_arraycopy_allocateInstance
  3. 修复后检查 Unsafe_Get*Unsafe_Put* 和 native transition 是否从热点头部消失。
  4. 剩余约 29.5% 差距再继续做内联、去虚化和代码尺寸对比;目前证据不足以把这部分归因给某个具体优化。

完整分析、数据和实验说明在[分析报告 (line 8)](/home/luofeng/jdk21-c3-jit/results/neo4j-c2-c3-analysis-20260731/analysis-report.md:8)。初始结果为 C2 1.567 秒、C3 9.323 秒,即 C3 慢 5.95 倍,见[五项对比 (line 19)](/home/luofeng/jdk21-c3-jit/results/renaissance-c2-c3-selected-20260731_1036/assessment.md:19)。


par-mnemonics 的主因已定位:C3 缠失 _arraycopy_allocateInstance intrinsic,可以因果解释大约一半劣化。

实验 稳态时间
C2 3.315 s
C3 11.043 s
C2 禁用 _arraycopy 5.607 s
C2 禁用 _allocateInstance 4.984 s
C2 禁用全部已观察缺失 intrinsic 7.278 s

组合实验解释 C3/C2 总差距约 51.3%

核心证据:

  • C3 profile 中 System.arraycopy + JVM_ArrayCopy 占 14.39%,包括数组复制内部路径约 15.55%。
  • Unsafe.allocateInstance + Unsafe_AllocateInstance + ObjAllocator 占约 9.77%。
  • native 状态切换另外占 8.77%。
  • C2 profile 中这些 VM fallback 均未进入热点头部。[C3 热点原始数据 (line 12)](/home/luofeng/jdk21-c3-jit/results/par-mnemonics-c2-c3-analysis-20260731/c3-profile/perf-report.txt:12)

源码也明确证实:

  • [C3 intrinsic 白名单 (line 163)](/home/luofeng/jdk21-c3-jit/src/hotspot/share/jeandle/jeandleIntrinsicLowering.cpp:163)没有 _arraycopy_allocateInstance_vectorizedHashCode_isPrimitive
  • C2 支持 [`_vectorizedHashCode` (line 216)](/home/luofeng/jdk21-c3-jit/src/hotspot/share/opto/c2compiler.cpp:216)、[`_arraycopy` (line 578)](/home/luofeng/jdk21-c3-jit/src/hotspot/share/opto/c2compiler.cpp:578)、[`_allocateInstance` (line 687)](/home/luofeng/jdk21-c3-jit/src/hotspot/share/opto/c2compiler.cpp:687)。

剩余约一半表现为代码质量和内联反馈问题:

  • C3 指令数是 C2 的 2.40 倍。
  • IPC 从 2.69 降到 2.02。
  • cache-miss rate 从 0.481% 升到 0.862%。
  • HashMap.computeIfAbsentgroupingBy$53wrapAndCopyInto 等热方法的 C3 机器码约为 C2 的 1.7 倍。
  • C2 会把 ArrayList.add → add → grow → grow 整条链内联,[C2 inline 证据 (line 1565)](/home/luofeng/jdk21-c3-jit/results/par-mnemonics-c2-c3-analysis-20260731/c2-inlining/process.log:1565);C3 因 already compiled into a big method 提前停止,[C3 inline 证据 (line 1520)](/home/luofeng/jdk21-c3-jit/results/par-mnemonics-c2-c3-analysis-20260731/c3-inlining/process.log:1520)。

其他缺口影响较小:

  • 缺少 Java heap escape analysis:C2 关闭后劣化约 8.9%。
  • 缺少 C2 dedicated StringConcat 优化:约 4.4%。
  • _vectorizedHashCode:单独约 2.4%。
  • _isPrimitive:基本无影响。

建议修复顺序:

  1. _arraycopy
  2. _allocateInstance
  3. 修复后重新评估代码尺寸和内联反馈
  4. _vectorizedHashCode
  5. escape analysis 和一般代码生成质量

完整数据和实验记录在[分析报告 (line 8)](/home/luofeng/jdk21-c3-jit/results/par-mnemonics-c2-c3-analysis-20260731/analysis-report.md:8)。


page-rank 分析完成。核心结论:C3 是 C2 的 3.163 倍,第一主因是 C3 缺少普通 Unsafe.get/put intrinsic。

关键证据:

  • C2 禁用 12 项普通 Unsafe intrinsic 后,可解释 32.7% 的总性能差距。
  • 加上 allocateInstance、identity hash、类型检查和 arraycopy 等缺失项,可解释约 56.9%
  • C3 profile 中普通 Unsafe fallback 占 24.05%,native transition 占 14.45%;C2 基本没有这些开销。
  • C3 动态指令数为 C2 的 2.846 倍,IPC 下降 15%;cache miss rate 反而更低,排除 cache miss 是主因。
  • 剩余约 43% 与内联、代码膨胀和逃逸分析差异有关,目前尚未完全拆分。部分 C3 热点代码尺寸是 C2 的 1.4–3.3 倍。

建议修复顺序:

  1. 普通 Unsafe load/store
  2. Unsafe.allocateInstance
  3. identity hash、arraycopy、类型/数组 intrinsic
  4. Spark aggregation/TimSort 内联与代码尺寸
  5. 逃逸分析

完整结论、A/B 实验、profile 和源码证据见:analysis-report.md


philosophers 分析完成。初始同批结果 C3/C2 为 2.768x(+176.8%),主要是 intrinsic 缺口被 64 线程 Scala STM 冲突重试放大。

核心结论:

  • 最大单项:C3 缺少 _isInstance

    • C3 profile 中 Class.isInstance + jni_IsInstanceOf12.17% cycles
    • C2 禁用后从 5.829 s 升至 9.211 s,约可解释初始差距的 30.6%
  • 第二主因:缺少 _compareAndSetReference_getReferenceVolatile

    • C3 明确进入 Unsafe/VM fallback,C2 没有。
    • C2 禁用后升至 8.322 s,约对应初始差距的 22.6%
  • C3 profile devirtualization 是额外放大器

    • 三组成对实验平均让 C3 再慢 1.748 s / 11.8%
    • 但关闭后 C3 仍约 14.9 s,说明它不是全部根因。
  • _allocateInstance 是次要缺口,C2 禁用后约慢 8.3%

  • 逃逸分析在本项影响只有约 3%,处于运行波动范围。

  • C3 使用的 CPU 核并不少,但受扰动的 PMU 实验中执行了 C2 的 7.43 倍指令、产生 17.73 倍 branch miss,属于额外动态工作和事务反馈,而不是简单并行度不足。

这些比例不能直接相加:组合禁用实验出现非线性结果,证明编译代码形态会改变 STM 冲突、rollback 和 retry 稳态。

建议修复顺序:

  1. _isInstance
  2. reference CAS 和 volatile reference access 家族
  3. _allocateInstance
  4. 补齐 intrinsic 后重新约束 profile-devirtualization 的 guard/CFG/code-size 收益模型

完整报告和全部源码、profile、A/B 证据见:analysis-report.md


reactors 分析完成,完整报告见 analysis.md

核心结论:

  • C3 为 33.880 秒,C2 为 20.388 秒,C3 慢 1.662x / 66.2%,绝对增加 13.491 秒。

  • 主因是动态指令膨胀:C3 instructions 为 C2 的 2.71x,cycles 为 2.07x;不是单纯 CPU stall。

  • 劣化集中在跨 reactor 调度、转发和创建:

    • ForkJoinThroughput:贡献 42.5%
    • Roundabout:16.9%
    • PingPong:12.4%
    • ThreadRing:11.4%
    • ForkJoinCreation:10.2%

    五项合计贡献 93.4%;CountingActor 没有劣化。原始拆分数据见 C2C3

代码形态上,C3 的 $bang$extension 机器码是 C2 的 2.51x,并占 C3 cycles 的 5.35%;C3 有 37 次因已编译代码过大而拒绝内联,C2 只有 6 次。但调大内联阈值、强制内联 $bang$extension 均没有改善,所以这只是整体代码质量问题的表征,不能归因于单一内联点。

C3 确认缺少本 workload 命中的这些 C2 intrinsic:

  • 引用 CAS、compareAndExchangeLong
  • Unsafe 普通/volatile 访问
  • Class.isInstance/isArray
  • arraycopy
  • allocateInstance

白名单证据见 jeandleIntrinsicLowering.cpp。它们造成 JNI/runtime fallback,但 profile 上限约 8%–10%,C2 禁用实验也未出现稳定退化,因此是次要项,解释不了 66%。

另外,C3 allocation elimination 目前只是保守子集,而且尚未接入当前编译流水线;对象初始化还使用 atomic store 和临时 release fence。不过 EA、ZeroTLAB A/B 都不支持它们是本次主因。

建议优先以 ForkJoinThroughput 为最小复现,检查:

$bang$extension → send → enqueueEvent → processEvents/executeBatch → ForkJoinPool

重点比较 C2/C3 LLVM IR 和 AArch64 汇编中的重复类型检查、oop/klass load、barrier、CAS/volatile lowering及未消除分支。PMU 原始证据在 C2 perf-statC3 perf-stat

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions