← 返回 PaperDaily
前沿研究
浙大新研究:编译器优化当“瘦身教练”,指针分析最高提速3.14倍
编译器优化还能这么玩?浙大团队把语义保持的编译器变换直接用在指针分析之前做“离线瘦身”,结果最高3.14倍加速、1.94倍内存缩减。方法分析无关、即插即用,实验扎实,属于能直接拿来用的实用型研究。
龙哥读论文
发布于 2026-08-22 00:20:02
阅读 3
查看原文
原论文信息如下:
谁懂啊家人们,跑一次大规模C/C++工程的指针分析,等上几个小时是家常便饭,碰上超大项目直接内存爆掉、运行超时,那叫一个酸爽🤚。程序员们为了给指针分析提速,可以说是八仙过海各显神通:有人改进算法,有人并行化,有人搞在线图简化。但今天龙哥要聊的这篇论文,角度那叫一个清奇——直接让编译器优化来当“瘦身教练”,在指针分析之前先把程序给“搓扁揉圆”了,竟然效果出奇地好!
引言
先把背景说清楚。指针分析是静态分析领域的一块基石,编译器优化、程序切片、Bug检测、形式化验证,哪个都离不开它。它的任务就是搞清楚程序里一个指针变量在运行过程中可能指向哪些内存位置。听起来简单,但真实的大型程序里指针满天飞,再加上结构体、数组、函数指针、类型转换这些操作,分析复杂度直接起飞。业界苦指针分析性能久矣,多年来各路大神提出了各种加速方案,大致可以分成两类:一类是守住精度提性能,比如稀疏化、剪枝、并行化、增量分析;另一类是拿精度换规模,比如调节各种灵敏度、换堆抽象模型。
除了在分析算法本身上做文章,另一个思路是“预处理”——在分析开始之前,先对输入程序动点手脚,把问题规模缩小,这就是论文里说的离线简化。但传统的离线简化有不少痛点:首先,它们往往跟特定的分析算法深度绑定,换一种分析就不灵了;其次,它们工作在高层抽象表示上,比如符号指针图,能做的简化规则比较受限;最后,它们对所有输入程序一视同仁,缺少针对具体程序的定制化优化。
这篇文章的新视角在于:与其死磕高层抽象表示上的图简化,不如直接在中间表示这一层发力。编译器优化在把源代码变成机器码的过程中,会做各种保持语义的变换来提升代码质量。这些变换中很多都会顺带把程序“整容”得更加简洁规整——死代码被删了、冗余变量被合并了、统一类型的向量化操作让指令结构更清晰了。把这些变换用在指针分析之前,不就等于给分析器喂了一份“洗好切好的菜”嘛!而且编译器优化本身是分析无关的,不管下游用哪种指针分析,这一套预处理通通适用。
方法概述:编译器优化当“瘦身教练”
这篇论文的核心思想可以用一句话概括:把编译器优化当作指针分析的前置处理器。具体来说,就是用各种语义保持的LLVM优化pass去处理待分析程序的中间表示,然后把优化后的IR喂给指针分析器。整个流程是:源代码编译成LLVM IR → 应用一系列优化pass → 对优化后的IR做指针分析。
要理解这个方法为什么管用,得先了解编译器优化到底对指针分析产生了什么影响。编译器优化有很多种,按粒度可以分成指令级(比如lower-switch)、循环级(比如loop-flatten)、函数级(比如simplify-cfg、newgvn)、模块级(比如wholeprogramdevirt)。它们都会不同程度地改变IR的结构。表1总结了LLVM中一些典型优化pass的分类。
*表格超出部分左右可以滑动
Table 1. Examples of LLVM optimizations
编译器优化在静态分析中的应用并不稀奇,标准化和简化是两个经典用途。比如loop-simplify能把循环变成规范形式,方便后续做循环分析;merge-return确保每个函数只有一个出口点,简化数据流分析。还有不少静态分析工具依赖特定的IR形式,比如SSA或没有不可约循环的控制流图。但把这套思路系统性地用到指针分析的离线简化上,这篇论文算是第一次。
论文采用的优化策略是“迭代式优化”,这是编译器社区的一个经典方法:对同一个程序用不同的编译选项编译多遍,挑出效果最好的配置。具体到这篇工作,研究团队选择了105个LLVM优化flag,每个程序随机生成300个优化序列,从中找出能让指针分析最快的那个配置。这个搜索过程虽然看起来“笨”,但胜在通用和有效。
那么这种编译器驱动的离线简化到底赢在哪里?论文总结了三大优势:
广泛适用性 编译器优化不关心下游指针分析具体是什么流派。不管是流不敏感还是流敏感,不管是用集合约束还是上下文无关语言可达性,都能直接受益。这种分析无关性是传统简化方法做不到的。
丰富的简化机会 编译器优化直接在IR上工作,保留程序的完整语义,能做很多超越传统图简化的花样。比如newgvn优化是流敏感的,能同时处理内存变量和标量变量,还能推理控制流和数据流,这比在抽象的符号指针图上做等值合并强多了。
实例特定的简化 不同程序的IR结构千差万别,编译器变换可以针对每个具体的程序实例定制优化配置,做到“因材施教”,挖掘出统一策略错过的性能提升机会。
数据准备及实验设计
为了系统地回答“编译器优化到底对指针分析有多大帮助”这个问题,研究团队设计了一套严谨的实验方案,从基准程序、分析器选择、优化配置搜索到性能评估指标,每一个环节都考虑得很周到。
论文选取了22个真实世界中的开源C/C++项目作为基准测试集,这些项目覆盖了多种应用场景,包括构建系统(ninja)、视频编码库(x264、vvenc)、终端复用器(tmux)、HTTP服务器(nginx、trafficserver)、数据库引擎(sqlite)、脚本语言解释器(perl、python、php)、C编译器(gcc)、文本编辑器(vim)等。这些项目规模差异很大,从2.5万行代码到284万行代码不等,充分考验了方法在不同规模程序上的表现。所有实验在配备28核Intel Xeon Platinum 8176处理器和512GB内存的服务器上进行,每个测试案例限时12小时、限内存128GB。
在指针分析器的选择上,论文涵盖了三种主流且各具代表性的实现,均基于SVF框架:第一种是DW-ander,带波传播启发式的Andersen分析;第二种是SCD-ander,带选择性循环检测的流不敏感和上下文不敏感Andersen分析;第三种是VSFS,基于SFS的流敏感、上下文不敏感分析,并融合了对象版本化技术。从流不敏感到流敏感,从简单到复杂,这三种分析器为评估编译器优化的通用性提供了丰富的维度。
对于编译器优化的配置搜索,论文选择了105个LLVM优化flag,并采用随机搜索策略为每个程序生成300个不同的优化序列。表2给出了实验中的一些术语定义。这种基于随机搜索的迭代优化方法,在编译器社区已经是一种成熟的技术路线,其出发点在于编译器优化pass之间的复杂交互很难用理论模型预测,与其手工挑选优化组合,不如让数据说话。
*表格超出部分左右可以滑动
Table 2. Definitions and terminology. An optimization configuration, denoted as o, is a set of compilation flags, each associated with a specific value.
研究团队从四个维度来评估编译器优化的效果:一是端到端的性能提升,即包含优化开销在内的总时间对比;二是IR结构的变化,比如指针数量、指令数量;三是单独优化pass的贡献,找出哪些pass是关键所在;四是对分析精度的影响,用点集大小、别名对比率、调用图边数、可达方法数等指标量化。这四个维度也对应着论文提出的四个研究问题。
实验结果:三个分析器统统提速
表4展示了三种指针分析在基准配置和最优配置下的运行时间对比。可以看到编译器优化在大多数程序上都带来了显著的时间缩减。比如nginx项目,三种分析的执行时间都减少了60%以上;zsh项目减少了超过45%。不过也注意到一些程序出现了轻微回退,比如ninja这种本身只有2.5万行代码的小程序,优化带来的额外开销反而把收益给抵消了,属于“优化了个寂寞”,这也说明优化并非对所有程序都稳赚不赔。
*表格超出部分左右可以滑动
Table 3. List of benchmarks and their characteristics. Lines of code (LoC) are obtained using cloc.
编译器优化也能给指针分析"瘦身"?——一个新视角的登场
如果说传统的离线简化是"照着地图找近路",那这篇论文的思路就是"直接把地图重新画一遍"。前面已经提到,传统简化技术有三个老毛病:跟特定分析算法深度耦合、只能在高层的抽象图上做有限操作、对所有程序一视同仁。这就像一个健身教练只会一套训练计划,不管学员是高矮胖瘦全都照着一个方子练,效果自然参差不齐。
编译器优化则完全不一样。它工作在LLVM IR这一层,根本不管下游做什么分析。更重要的是,编译器优化是语义保持的——它把程序变快、变小、变规整,但不会改变程序的行为。这种性质放到指针分析里,就变成了一个天然合规的"离线简化器"。
论文把这种思路的优势总结得很清楚。第一,分析无关 ——不管下游用的是流不敏感的Andersen分析,还是流敏感的VSFS,也不管底层是集合约束、上下文无关语言可达性还是声明式语言实现,所有分析都能无差别受益。第二,简化机会更丰富 ——比如newgvn这个优化pass,它既处理标量又处理内存,还能推理控制流和数据流,其威力远超过在符号指针图上合并几个等价节点。第三,实例特定 ——每个程序的IR长什么样、冗余集中在哪里,编译器优化都可以针对性地配置,这种"因材施教"是传统统一规则做不到的。
而且,从工程角度看,编译器驱动的离线简化是模块化、可插拔的。一个库可以被简化一次,然后在不同的客户端程序和不同的指针分析之间复用;编译器社区一旦推出新的优化pass,分析工具不用改一行代码就能享受红利;编译器优化还能跟传统的约束图简化形成互补——先把IR洗一遍,让约束图更小更干净,后端的图简化效果自然也会更强。这套"洗切配"一条龙服务,确实让人眼前一亮。
方法揭秘:语义保持的IR变换如何做到"分析无关"与"即插即用"
整个流程并不复杂:先把C/C++源代码编译成LLVM IR,然后应用一个优化pass序列,最后把优化后的IR喂给指针分析器。问题的关键就变成了"选哪些优化pass,按什么顺序跑"。编译器优化pass之间的交互以复杂著称,没人能拍着胸脯说哪几个pass组合一定有效。论文采用的是编译器社区经典的迭代式优化思路:随机搜索。具体来说,从105个LLVM优化flag中随机生成优化序列,每个程序试300个不同的序列,跑完指针分析后,挑出端到端时间最短的那个配置,记为OpT。表2给出了实验中的术语定义。这种"让数据说话"的做法虽然看起来有点"笨",但胜在通用和可靠。
为什么说这套方法是分析无关的呢?因为优化完全发生在IR层面,与下游分析器的内部实现零接触。论文在SVF框架上验证了三种差异很大的指针分析:DW-ander ——带波传播启发式的流不敏感Andersen分析;SCD-ander ——带选择性循环检测的流不敏感和上下文不敏感Andersen分析;VSFS ——基于SFS的流敏感、上下文不敏感分析,融合了对象版本化技术。三种分析的精度和算法实现完全不同,但都能从同一套优化配置中获益,这本身就证明了分析无关性不是一句空话。
编译器优化的粒度覆盖了从单条指令到整个模块的各个层级,表1列出了LLVM中部分典型优化pass及其所在层级,比如指令级的lower-switch、函数级的newgvn和simplify-cfg、模块级的ipsccp和wholeprogramdevirt等。这些pass的出发点都是为了生成更高效的机器码,但本文发现它们对IR结构的改造——删死代码、合并等价变量、规整控制流——恰好也是指针分析最需要的"减负"操作。
基准程序方面,论文选择了22个真实开源项目,从2.5万行的ninja到284万行的php,覆盖构建系统、HTTP服务器、数据库、编译器、解释器等各种形态,如表3所示。实验环境是28核Intel Xeon Platinum 8176处理器、512GB内存,每个案例限时12小时、限内存128GB。论文围绕四个研究问题展开:端到端性能提升多大?IR结构发生了什么变化?哪些pass贡献最大?精度受了多大影响?
实验结果:最高3.14×加速、1.94×内存缩减背后的关键发现
表4给出了三种分析在所有22个程序上的端到端运行时间。编译器优化在大多数程序上都带来了显著收益:nginx上三种分析的运行时间都减少了60%以上,zsh上减少了超过45%,gcc的DW-ander分析获得了约35%的加速,ctags的VSFS分析也接近这个水平。最高加速比出现在nginx的SCD-ander上,达到了3.14倍;DW-ander在nginx上也取得了2.70倍的加速。内存方面,VSFS分析nginx时峰值内存减少了约50%,SCD-ander分析tmux减少了13%,整体峰值内存缩减最高达1.94倍。
不过,"全赢"是不存在的。ninja这种只有2.5万行代码的小程序,优化开销反而超过了分析收益,出现了轻微回退。这给了一个很实在的提醒:编译器优化这套"瘦身术"更适合中大型程序,小项目不如省掉预处理直接分析。
图1用热力图直观展示了22个程序在三种分析下的最佳加速比和最佳峰值内存比分布。可以看到,深色块主要集中在SCD-ander和VSFS这两列,说明流敏感分析从IR简化中获益更大。图2则展示了所有配置的加速比和内存比分布,曲线总体呈偏态分布,说明大多数配置都有正向效果,但真正的高收益配置集中在少数几个优化序列里。
论文还把自定义最优配置OpT跟标准的O1、O2、O3优化级别做了对比,结果如图3所示。有趣的是,OpT并不总是全面碾压O3,有时候O3甚至比OpT更快。但普遍来说,OpT的加速比分布更稳定、下限更高。这说明"随机搜索+针对性挑选"确实能找到比通用优化级别更适合特定程序的配置,但标准优化级别在多数场景下已经是性价比很高的选择。
还有一个细节值得注意:omnetpp在DW-ander和VSFS下都触发了超时上限,只有SCD-ander在优化配置下跑完了,而且比DW-ander(OpT)快18.92倍、比VSFS(OpT)快44.08倍。这说明编译器优化和选择正确的分析器是两个正交的维度,叠加起来收益更大。
Pass级深度剖析:mergefunc、strip与load-store-vectorizer谁才是功臣?
加速效果有了,那究竟是哪些pass在背后默默出力?论文通过两组实验来回答这个问题:一组是统计最优配置中哪些pass总被保留、哪些总被移除(图6),另一组是把某个pass单独禁掉,观察分析时间的变化(图7)。
先看整体结论:不同程序的最优pass集合差异很大,这说明"实例特定"并非虚言。有些pass几乎在所有程序的最优配置中都出现,属于"泛化功臣";有些pass只在特定程序上带来巨大收益,换个程序就失效甚至起反作用。表5列出了部分这类上下文相关的pass,百分比表示禁用对应pass后分析时间的相对变化。
具体到pass层面,论文的实验数据里,像mergefunc (合并等价函数)、strip (去除IR中的符号附加信息)、load-store-vectorizer (加载存储向量化)这类pass,在一些大型程序上表现相当亮眼。mergefunc把实现相同的函数合并成一个,约束图上的节点和边数量自然就少了;strip清理了IR里调试符号等不影响语义的附加信息,减轻了分析的解析开销;load-store-vectorizer把分散的加载存储操作向量化,让内存访问模式更规整,也间接减少了指针相关操作的复杂程度。
不过这里要泼一盆冷水:这些pass的效果高度依赖程序特征。图7的时间变化热力图显示,同一个pass在某些程序上能带来两位数的加速,在另一些程序上反而拖后腿。所以论文强调,与其手工挑选"万能pass组合",不如保留随机搜索这套机制,让每个程序自己"投票"选出最合适的配置。图5展示了优化后IR中各类指令占比的变化,可以看出指令结构确实被显著重塑。
精度影响与未来方向:一场尚未结束的探索
加速之余,精度是绕不开的坎。编译器优化毕竟是语义保持的,理论上不该改变指针分析的结果,但实际上IR结构变了,分析器在近似求解过程中的行为也会跟着变。论文用四个指标来量化精度变化:点集大小(points-to set size)、别名对比率、调用图边数和可达方法数,如表6所示。
图4和图8展示了优化前后IR结构变化率和精度变化率。整体来看,优化前后的精度变化非常小,绝大多数程序的点集大小和别名对比率变化都在2%以内,没有出现系统性偏差。少数程序有略微的精度提升或下降,但幅度都在可接受范围内。这说明编译器优化在"几乎不损失精度"的前提下,换来了可观的性能收益,这笔买卖确实划算。
当然,这项工作更像是一扇门的开启,而不是终点。论文在最后指出了几个值得深耕的方向。第一,当前随机搜索的成本不低——为每个程序试300个配置,虽然可以离线做、结果可复用,但仍有优化空间,比如用机器学习来预测哪些pass组合可能有效。第二,编译器优化与现有约束图简化之间存在潜在的协同效应:先做IR清洗、再做图简化,两者叠加的效果值得进一步验证。第三,把这种思路推广到更多语言和更多静态分析任务——毕竟IR层面的优化离"分析无关"只有一步之遥。
还有个很有意思的副产品:在实验过程中,研究团队发现了12个SVF框架中此前未知的bug,其中一部分已经被开发者修复。这种"跑实验跑出新bug"的遭遇,干过静态分析的人都懂,只能说一句:真实世界的代码,永远比你想象的更有"惊喜"。
龙迷三问
这篇论文到底在解决什么问题? 本文介绍浙江大学一项新研究,创新性地将编译器优化作为指针分析的离线简化手段,在22个真实开源项目上验证了其效果,最高带来3.14倍加速、1.94倍内存缩减,且精度基本不变。
这篇工作最值得看的点是什么? 编译器优化在大多数基准上显著减少分析时间,最高达到3.14×加速(SCD-ander在nginx上)和1.94×内存缩减(VSFS在nginx上);VSFS三类分析中受益最大;标准优化级别(O1/O2/O3)通常不如定制配置OpT;精度指…
这篇工作的边界或风险在哪里? 优点:(1) 视角新颖,将编译器优化引入指针分析的离线简化,突破传统图简化方法在抽象表示层面的限制;(2) 分析无关性,可适用于不同精度和不同形式的指针分析;(3) 模块化、即插即用,易于集成到现有工具链;(4) 实证研究系统全面,覆盖2…
如果你还有哪些想要了解的,欢迎在评论区留言或者讨论~
龙哥点评
论文创新性分数: ★★★★☆
把编译器优化系统性地引入指针分析离线简化,视角新颖且自然,填补了一个重要空白。
实验合理度: ★★★★★
22个真实程序、3种差异明显的分析器、随机搜索加消融实验,结构严谨,结论可信。
学术研究价值: ★★★★☆
为静态分析加速提供了新方向,打开了编译优化与程序分析交叉研究的大门。
稳定性: ★★★☆☆
整体正向,但存在程序间差异,小项目上可能出现负收益,需结合程序规模决定是否启用。
适应性以及泛化能力: ★★★★☆
分析无关、IR层面通用,对C/C++全适用;扩展到其他语言和更多静态分析场景的潜力很大。
硬件需求及成本: ★★★☆☆
随机搜索300个配置的离线成本较高,但实际部署时可选用O2/O3等标准级别,几乎零额外成本。
复现难度: ★★★★★
代码和实验数据已公开,基于开源的SVF框架,工具链完整,复现门槛低。
产品化成熟度: ★★★★☆
基本可以直接用,推荐对中大型程序启用O2或O3预处理,小型程序建议保持原样。
可能的问题: 论文以实证为主,对"为什么这些pass有效"缺少理论层面的解释;随机搜索的运气成分未被讨论;对小程序的负收益缺乏更细的指导策略。但整体瑕不掩瑜。
[1] Zinan Gu, Peisen Yao, Kui Ren. Accelerating C/C++ Pointer Analysis via Compiler-Based Offline Simplifications. 2026. https://arxiv.org/pdf/2608.04466v1.pdf
[2] SVF: Interprocedural Static Value-Flow Analysis in LLVM. https://github.com/SVF-tools/SVF
[3] 论文开源代码与实验数据: https://anonymous.4open.science/r/opt4pta-D464/
[4] Yulei Sui, Jingling Xue. SVF: interprocedural static value-flow analysis in LLVM. CC 2016.
*本文仅代表个人理解及观点,不构成任何论文审核或者项目落地推荐意见,具体以相关组织评审结果为准。欢迎就论文内容交流探讨,理性发言哦~ 想了解更多原文细节的小伙伴,可以点击
*本文仅代表个人理解及观点,不构成任何论文审核或者项目落地推荐意见,具体以相关组织评审结果为准。欢迎就论文内容交流探讨,理性发言哦~ 想了解更多原文细节的小伙伴,可以点击 "阅读原文", 查看更多原论文细节哦!