Python 为什么Z3对于微小的搜索空间来说速度很慢?

Python 为什么Z3对于微小的搜索空间来说速度很慢?,python,z3,boolean-logic,z3py,Python,Z3,Boolean Logic,Z3py,我正在尝试用Python编写一个Z3程序,该程序生成执行某些任务的布尔电路,例如添加两个n位数字,但性能非常糟糕,对整个解决方案空间进行暴力搜索会更快。这是我第一次使用Z3,所以我可以做一些影响性能的事情,但我的代码似乎很好 以下内容是从我的代码中复制的: 代码基本上将输入设置为单个位,然后在每一步,5个布尔函数中的一个可以对上一步的值进行操作,其中最后一步表示最终结果 在本例中,我生成了一个电路来计算两个1位输入的布尔OR,并且电路中有一个OR函数,因此解决方案很简单 我的解空间只有5*5*2

我正在尝试用Python编写一个Z3程序,该程序生成执行某些任务的布尔电路,例如添加两个n位数字,但性能非常糟糕,对整个解决方案空间进行暴力搜索会更快。这是我第一次使用Z3,所以我可以做一些影响性能的事情,但我的代码似乎很好

以下内容是从我的代码中复制的:

代码基本上将输入设置为单个位,然后在每一步,5个布尔函数中的一个可以对上一步的值进行操作,其中最后一步表示最终结果

在本例中,我生成了一个电路来计算两个1位输入的布尔OR,并且电路中有一个OR函数,因此解决方案很简单

我的解空间只有5*5*2*2*2*2=400:

5个可能的功能两个功能节点 每个功能有2个输入,每个输入有两个可能的值 这段代码需要几秒钟的时间来运行并提供正确的答案,但我觉得它应该立即运行,因为只有400种可能的解决方案,其中很多是有效的。如果我将输入增加到两位长,则解决方案空间的大小为5^4*4^8=40960000,并且永远不会在我的计算机上结束,尽管我觉得这在Z3中应该很容易实现

我还有效地尝试了相同的代码,但用array/Store/Select替换了Python列表,并使用chooseFunc中使用的相同技巧选择了变量。代码是,并且它在与原始代码几乎相同的时间内运行,因此没有加速

我是否正在做一些会大大减慢解算器速度的事情?谢谢

您的操作列表中有一个重复的异或;但这并不是主要问题。随着位大小的增加,速度的减慢是不可避免的,但乍一看,您可以而且应该避免将整数推理与布尔运算混合在一起。我将为您的chooseFunc编写如下代码:

def chooseFunci,x,y: res=假; 对于ind,枚举操作列表中的操作: res=Ifind=i,op x,y,res 返回res
看看这是否以任何有意义的方式改善了运行时间。如果没有,下一步要做的就是尽可能地去掉数组。

chooseFunc看起来非常笨拙,可能会产生过于复杂的输出。你为什么不直接做op_list[i]x,y?啊。我不是整数。是的,理想情况下它应该是一个数组,我会选择我想要的函数,但我不知道如何在这样的数组中存储函数。非常感谢,这使它加快了很多!我不熟悉Z3的最佳实践,看起来小事情总是一件麻烦事,你知道一个寻找合适示例代码的好地方吗?我相信你已经看到了:还有一个很有趣的地方,虽然它混合和匹配了很多不同的东西,但并不总是一致的。如果您熟悉Haskell,可以查看SBV软件包附带的示例:
from z3 import *

BITLEN = 1 # Number of bits in input
STEPS = 1 # How many steps to take (e.g. time)
WIDTH = 2 # How many operations/values can be stored in parallel, has to be at least BITLEN * #inputs

# Input variables
x = BitVec('x', BITLEN)
y = BitVec('y', BITLEN)

# Define operations used
op_list = [BitVecRef.__and__, BitVecRef.__or__, BitVecRef.__xor__, BitVecRef.__xor__]
unary_op_list = [BitVecRef.__invert__]
for uop in unary_op_list:
    op_list.append(lambda x, y : uop(x))

# Chooses a function to use by setting all others to 0
def chooseFunc(i, x, y):
    res = 0
    for ind, op in enumerate(op_list):
        res = res + (ind == i) * op(x, y)
    return res

s = Solver()
steps = []

# First step is just the bits of the input padded with constants
firststep = Array("firststep", IntSort(), BitVecSort(1))
for i in range(BITLEN):
    firststep = Store(firststep, i * 2, Extract(i, i, x))
    firststep = Store(firststep, i * 2 + 1, Extract(i, i, y))
for i in range(BITLEN * 2, WIDTH):
    firststep = Store(firststep, i, BitVec("const_0_%d" % i, 1))
steps.append(firststep)

# Generate remaining steps
for i in range(1, STEPS + 1):
    this_step = Array("step_%d" % i, IntSort(), BitVecSort(1))
    last_step = steps[-1]

    for j in range(WIDTH):
        func_ind = Int("func_%d_%d" % (i,j))
        s.add(func_ind >= 0, func_ind < len(op_list))

        x_ind = Int("x_%d_%d" % (i,j))
        s.add(x_ind >= 0, x_ind < WIDTH)

        y_ind = Int("y_%d_%d" % (i,j))
        s.add(y_ind >= 0, y_ind < WIDTH)

        node = chooseFunc(func_ind, Select(last_step, x_ind), Select(last_step, y_ind))
        this_step = Store(this_step, j, node)

    steps.append(this_step)

# Set the result to the first BITLEN bits of the last step
if BITLEN == 1:
    result = Select(steps[-1], 0)
else:
    result = Concat(*[Select(steps[-1], i) for i in range(BITLEN)])

# Set goal
goal = x | y
s.add(ForAll([x, y], goal == result))

print(s)
print(s.check())
print(s.model())