SMT求解器的不稳定性,正在悄悄拖慢所有做形式化验证的团队
一
做程序验证的人,大概都碰到过这么一件事:同一段逻辑,换个写法,跑出来的结果可能天差地别。一段原本几秒就出答案的查询,换个等价的表达方式,可能直接超时。这不是编译器优化的差异,也不是硬件波动,而是SMT求解器本身的行为——它对输入的"长相"极其敏感,哪怕语义上完全等价。
这种现象有个名字,叫SMT不稳定性。简单说,就是语义等价的源代码排列组合,有的能秒解,有的直接超时。这个问题之所以恼人,是因为它 interrupt workflows,让软件工程师在"要不要用验证"这件事上反复犹豫。你今天验证通过了,明天同事换种写法,失败了,然后整个团队就开始怀疑验证到底有没有用。
二
以前不是没人研究过这个问题。但难处在于,SMT不稳定性产生的运行时分布,跟常见的统计模型不太一样。不是正态分布,不是均匀分布,长尾、突变、离群值一堆,传统的标准差或者极差根本刻画不了。你拿平均运行时去衡量,发现不了问题;拿最大运行时说事,又太容易被个别情况带偏。所以过去很多尝试,要么测出来的数字没法复现,要么测出来的结论没法指导实践。
这篇论文想做的事,就是把这个"测不准"的局面打破。他们选了一个很具体的统计量:变异系数,也就是标准差除以平均值。这个指标的好处是,它同时考虑了波动的绝对大小和相对于均值的比例,不会因为查询本身耗时长就显得波动大,也不会因为查询本身快就显得波动小。
三
他们建了一个很大的数据集,覆盖三个软件领域的SMT查询。不是实验室里的合成数据,是从真实项目里收集来的。然后对这些查询做语义等价的排列组合,每种排列都跑一遍,记录运行时,最后算出每组查询的变异系数。
结果有几个发现,都很值得琢磨。
第一,SMT不稳定性在每个领域都显著存在。不是某个领域、某类查询的特有现象,而是普遍问题。这意味着,只要你用SMT求解器做验证,就绕不开这个坎。
第二,不稳定性随着查询平均耗时的增加而增加。也就是说,越难解的查询,越容易出现"换个写法就崩"的情况。这对实际影响很大——简单的查询你可能感觉不到,一旦涉及复杂逻辑,风险指数级上升。
第三,也是这篇论文最值得说的一点:他们提了一个跟以往不同的思路。过去大家想的是,怎么把不稳定性量出来,然后报告给开发者,让开发者自己去调。但调查询结构这件事,本身就很反人性——你不知道哪种写法对求解器友好,试错成本又高。所以他们的建议是,与其优化方差,不如直接优化运行时。也就是说,让求解器在选择求解策略的时候,优先考虑"能不能快点算出来",而不是"波动大不大"。这样既降低了不稳定性,还顺带提升了整体求解性能。
四
这个思路其实挺务实的。传统做法是把不稳定性当成一个需要单独治理的问题,结果往往是开发者在"用不用验证"和"怎么写查询"之间来回纠结。新思路则是把不稳定性当成一个副产品——只要你让求解器跑得快,波动自然就小了。这跟软件工程里很多优化问题的逻辑是一致的:不要针对副作用去打补丁,要从根上把主流程做好。
不过,这个方向要落地,还有不少工作要做。比如,怎么让求解器在求解过程中实时评估查询结构的友好程度?怎么在不同的求解策略之间做权衡?这些都不是这篇论文能直接回答的,但它确实把问题从"怎么衡量"推进到了"怎么解决"。
五
对做形式化验证的团队来说,这篇论文的结论有几个可以直接拿去用的判断。
如果你的验证流程经常因为超时而失败,别急着怀疑硬件或者并发问题,先看看是不是查询结构本身不稳定。换一种等价的写法,可能就通了。
如果你的团队在"要不要上验证"这件事上犹豫不决,可以把不稳定性作为一个风险项来评估。不是所有查询都稳定,复杂查询尤其如此,这不代表验证没用,而是需要针对求解器的行为做适配。
如果你在选型或者调优,关注点可以从"平均性能"扩展到"稳定性表现"。一个平均很快但波动巨大的求解器,可能不如一个平均略慢但稳的选项来得可靠。
六
SMT求解器的不稳定性,本质上是自动化验证从"理论上能做"到"工程上好用"之间的一道坎。这篇论文没给出一个完整的解决方案,但它把坎的高度量清楚了,还指了一条可能的路。对于真正想把验证用起来的团队来说,这种量化的认知,比一堆平均性能数字有用得多。
毕竟,做验证的人最怕的不是慢,是不确定。今天能过、明天不能过,比一直慢但稳定,更容易让人放弃。
我之前就吃过这个亏,当时写完一段逻辑验证,本地跑得好好的,一提交到CI直接超时。查了半天发现,只是把几个变量的顺序换了一下,求解器就从几秒变成了几分钟。后来学乖了,凡是关键路径上的验证,都得先在本地把各种写法都试一遍,挑最快的那版提交。这玩意儿比调参还玄学。