LATX, AOT: statically reduce L1I set conflicts - #435
Draft
LaurenIsACoder wants to merge 3 commits into
Draft
Conversation
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
This work started from an unexpected result in the EFLAGS optimization effort: the translator emitted fewer LoongArch instructions, but several SPECint workloads did not become faster and some became slower. Since the translated instruction count had already decreased, we treated the remaining regression as a separate code-fetch and layout problem instead of concluding that EFLAGS elimination had no value.
PMU measurements showed that performance could change significantly when the generated LoongArch code moved in the AOT code buffer, even when the instruction sequence and count remained effectively unchanged. This drew attention to L1 instruction-cache set conflicts.
The validation host is a Loongson 3A6000 with a 64 KiB, 4-way L1I, 64-byte lines, and 256 sets. Its set-index cycle is 16 KiB and a line maps approximately as:
Analysis of the existing layout
We compared the x86 ELF layout with AOT=2 LoongArch addresses for all 12 SPEC CPU2000 integer workloads:
The existing AOT implementation already preserves guest TU order and naturally excludes nearly all compiler-marked cold code. The main lost property was larger address alignment: 16/32/64-byte alignment was preserved at 99.1%/98.6%/97.7%, while 512-byte alignment was preserved at 10.5% and 2/4 KiB alignment at 0%.
An early experiment restored 512-byte to 4 KiB boundaries for direct-call targets and improved SPECint. Causal ablations showed that the boundary itself was not beneficial: the original functions had at most 15 bytes of preceding padding, exact-512-only and 4-KiB-only variants regressed, and delaying padding to the following TU kept most of the gain even though the call target was no longer aligned. Sampling showed that downstream ordinary TUs, not the aligned entries, accounted for most saved misses. Sparse padding was changing the relative L1I-set placement of hot code groups.
Summary
ways * cachelinebytes before safe direct-CALL entries, under a 1% per-segment padding budgetThe feature is experimental, defaults off, and has one
LATX_AOT_STATIC_LAYOUT=0/1switch.Rationale
The previous guest-address-alignment experiment improved SPECint, but causal ablations showed that 512-byte or 4KB guest alignment was not itself beneficial. Its padding happened to change downstream LoongArch code placement across L1I sets. This version replaces the guest-address proxy with an explicit static set-pressure model.
Only direct-CALL targets may receive padding. Allowing padding before arbitrary TUs caused a real vortex wait-path failure because the current AOT recovery path has implicit layout constraints at ordinary TU boundaries.
Results
Loongson 3A6000, AOT=2, SPEC CPU2000 integer train, three-run medians, latest master
3de0d400f51:Geometric mean runtime: -0.366%. All 12 benchmarks passed.
Default AOT=1, same candidate binary and byte-identical AOT caches, switch off/on:
The AOT=1 path keeps per-thread, per-segment, per-CF_PARALLEL set pressure and clears it when the global TB flush count changes. Padding is restricted to direct-CALL entries, the same 1% stored budget, and 0-
wayscache lines. Its score includes both set overflow and the number of added cache lines.Default AOT=1 held-out SQLite, same binary and byte-identical caches, seven alternating off/on runs:
Held-out official SQLite 3.53.4 x86_64 workloads, not used to tune the algorithm:
sqlite3_analyzerruns: median cycles about -0.95%A deterministic random-padding control used the same safe boundaries, maximum padding, and global budget. In seven same-period alternating SQLite runs, the scored layout was about 2.17% faster than the random layout with essentially identical host instruction counts.
The hardware evidence is mixed across programs: vortex reduced L1I misses, while SQLite improved cycles even when aggregate L1I misses did not. The PR therefore remains draft and does not claim that L1I set conflicts are the only layout-sensitive effect; branch-predictor and iTLB interactions remain follow-up work.
Validation
ninjabuild on LoongArchlatx-aot-cache-reader: pass, including disk write/mmap header round triplatx-aot-file-publish: passlatx-aot-pe-load-address: passLATX_AOT_STATIC_LAYOUT=2exits with an errorgit diff --check: passKnown limitations
AOT_VERSIONConfiguration
背景
这项工作起源于EFLAGS优化中的一个反常结果:翻译器生成了更少的LoongArch指令,但部分SPECint程序没有变快,个别程序反而变慢。既然翻译指令数已经下降,我们没有据此否定EFLAGS消除,而是把剩余退化作为独立的取指和代码布局问题继续分析。
PMU测量显示,即使LoongArch指令序列和数量基本不变,只要代码在AOT code buffer中的位置改变,性能就可能明显变化,因此我们开始关注L1指令缓存set冲突。
验证机器Loongson 3A6000的L1I为64 KiB、4路、64字节cacheline、256个set,set索引周期为16 KiB:
对现有AOT布局的分析
我们比较了12个SPEC CPU2000 integer程序的x86 ELF布局和AOT=2恢复后的LoongArch地址:
现有AOT已经保持guest TU顺序,并通过只缓存实际翻译代码自然过滤掉绝大多数cold代码。主要丢失的是较大的地址边界:16/32/64字节对齐分别保留99.1%/98.6%/97.7%,512字节只保留10.5%,2/4 KiB为0%。
早期实验恢复直接CALL目标的512字节到4 KiB边界后SPECint出现正收益,但因果消融证明边界本身不是原因:原函数前最多只有15字节填充,只处理exact-512或4 KiB都会退化,把padding延迟到下一个TU仍能保留大部分收益。采样显示减少miss的主要是后续普通TU,而不是被对齐的入口。真正的作用是稀疏padding改变了多个热点代码组之间的L1I set映射。
方案
AOT存储阶段利用已有TB元数据构造TU控制流图,通过强连通分量识别静态循环,并把循环及其直接callee作为静态工作集合。算法不依赖ELF符号,因此stripped程序也可以使用。
存储时已知最终LoongArch
tu_size。每个组件维护256项set压力,代价为:只有直接CALL目标允许padding。任意TU边界padding曾使vortex进入错误等待路径,说明普通TU边界仍有隐含布局约束。候选固定为0到
ways条cacheline,在3A6000上是0、64、128、192、256字节;每个segment总预算为LoongArch AOT代码尺寸的1%。AOT=1评分还加入新增cacheline数量,避免为很小的冲突收益扩大代码跨度。AOT文件保存L1I结构、布局magic、每段预算、每个TU的组件ID、安全CALL入口标志和AOT=2固定padding。
AOT=2
AOT=2按固定顺序整段恢复,加载时只检查L1I结构、读取padding、检查highwater并应用,每个入口O(1)。某次计划padding无法应用时,停用该segment剩余计划,避免部分计划错位。
AOT=1
AOT=1是默认模式,每次按需加载连续四页,不能复用AOT=2固定序列。它读取存储的静态组件ID,按线程、segment和
CF_PARALLEL维护已加载代码的set压力。普通TU只登记footprint,安全CALL入口只评估五个有界位置;状态查询复用于整个四页批次,TB flush计数变化时清空,同一TU不会重复登记。这属于运行时布局而不是运行时profile:只记录静态选中的代码已放在什么位置,不记录执行次数。
性能
以下负数表示更快。
AOT=2 SPEC CPU2000 integer train
AOT=1 SPEC CPU2000 integer train
相同候选二进制、字节完全一致的AOT文件,只切换布局开关:
Held-out SQLite
官方SQLite 3.53.4 Linux x86_64工具未参与算法设计或参数选择,A/B输出哈希一致:
随机padding否证
固定种子随机对照使用相同安全入口、最大padding和1%预算,但不使用set评分。同一时段7组SQLite交替中,评分方案cycles中位数约3.355B,随机方案约3.429B,评分方案快约2.17%,host指令数一致。这说明收益不能只用任意padding解释。
验证
ninja构建;git diff --check及产品提交checkpatch。配置
已知限制