我正在使用Z3 for Java来检查具有未解释函数的术语的可满足性,例如(type(o)> 1或type(p)< 1)。我遇到了check()函数导致的性能问题。例如,运行solver.check()需要6 ms来处理一个非常简单的约束(类型(o)> 2和type(o)= 1)。
FuncDecl typeFun = ctx.MkFuncDecl("type", ctx.IntSort(), ctx.IntSort());
Expr o = ctx.MkConst("o", ctx.IntSort());
//type(o)
IntExpr to = (IntExpr)typeFun.Apply(o);
//type(o)=1
BoolExpr subExpr1 = ctx.MkEq(to, ctx.MkInt("1"));
//type(o)>2
BoolExpr subExpr2 = ctx.MkGt(to, ctx.MkInt("2"));
//type(o)>2 and type(o)=1
BoolExpr expr = ctx.MkAnd(new BoolExpr[] { subExpr1, subExpr2 });
solver.Assert(expr);
//this step will take 6 ms.
solver.check();
鉴于我的项目中实际约束的大小要大得多(但是每个术语都非常简单,例如类型(o1)= 1,类型(o2)> 1等)比这个例子还有数十亿这种约束需要解决:
1. check()的性能应该是这样的吗?
2.如果1的答案为是,是否有其他替代方法可以绕过性能问题?
提前致谢。
@ChristophWintersteiger: 我认为我系统中的很大一部分限制应该是SAT。我正在实现Java的指针分析,我正在使用Z3以自下而上的方式解决虚拟调用的潜在目标。假设我有一个虚拟的callsite v.foo(),这个callsite可能会根据v的动态类型调用不同的方法。所以对于每个被调用者foo(),我将引入一个约束类型(o)= T其中o是点 - 接收器v和T的集合是声明foo的类。约束意味着v.foo()可以调用T中的方法foo(),当它的一个动态点集合的类型为T.我当前系统中的所有约束都是一些线性算术,只有一个未解释的函数“类型(○)”。但由于我是以自下而上的方式分析调用图,因此可以扩展与每个虚拟调用点相关的约束,利用分析到达根级别并且已经解决了接收器的所有点到目标。
答案 0 :(得分:2)
solver.check()
调用本机.dll / .so,因此在执行调用之前会涉及相当多的开销。一旦.check()
返回,检查结果是否有错误就会产生另一些开销。因此,它可能需要6毫秒,因为应用程序正在使用Java API。
如果应用程序对如此高的性能至关重要,则可能无法使用C API,因为所有其他API都会产生一些非零开销。
附录:我已经对以下简单类型进行了一些分析:
long startTime = System.nanoTime();
int i = 0;
for (; i < 10000; i++) {
status = solver.check();
}
long elapsedTime = System.nanoTime() - startTime;
运行大约需要22秒。在此处调用的默认解算器确实在此特定实例上表现出次优性能。我们可以通过添加ignore_solver1
选项(或使用push / pop命令)强制Z3回退到较旧的解算器,即按如下方式设置它:
Solver solver = ctx.mkSolver();
Params solver_params = ctx.mkParams();
solver_params.add("ignore_solver1", true);
solver.setParameters(solver_params);
解决相同问题10k次现在需要~100ms(我得到9us / check)。
在这些命令上调用的解算器也支持增量求解,因此我们几乎可以免费添加push / pop命令:
for (; i < 10000; i++) {
solver.push();
status = solver.check();
solver.pop();
}
将10k检查的运行时间增加到~120ms。现在,将solver.add(expr)
移动到此循环会大大增加运行时间,即为此:
for (; i < 10000; i++) {
solver.push();
solver.add(expr);
status = solver.check();
solver.pop();
}
我的运行时间约为275毫秒,即接近我们之前的两倍。因此,此时,表达构建/添加所需的时间与求解时间具有相同的数量级。此时,瓶颈确实是Java API。
我或许应该补充一点,我只尝试了问题中提供的示例。很可能情况是,在稍微不同的示例中,求解器的行为不同,添加ignore_solver1
选项会表现得更糟。
答案 1 :(得分:1)
您可以尝试使用自定义策略。默认求解器使用了相当广泛的算法组合(我相信)。尝试使用简单的策略而不进行预处理,例如MkTactic("smt")
。