为什么在此 SBV/Z3 代码中 Int32 排序比 Integer 排序慢得多?
Why is Int32 sort much slower than Integer sort in this SBV/Z3 code?
为了学习 Z3,我尝试使用 Haskell 绑定 sbv
解决我最喜欢的 Advent of Code 问题之一(一个特别困难的问题,2018 day 23, part 2)。前方代码剧透...
module Lib
( solve
) where
import Data.SBV
puzzle :: [((SInteger, SInteger, SInteger), SInteger)]
puzzle = (\((x, y, z), r) -> ((literal x, literal y, literal z), literal r)) <$> [
((60118729,58965711,8716524), 71245377),
{- 999 more values that are large like the first one... -}
]
manhattan (a1, a2, a3) (b1, b2, b3) =
abs (a1 - b1) + abs (a2 - b2) + abs (a3 - b3)
countInRange pos =
foldr (\(nb, r) -> (+) $ oneIf (manhattan nb pos .<= r)) (0 :: SInteger) puzzle
answer = optimize Lexicographic $ do
x <- sInteger "x"
y <- sInteger "y"
z <- sInteger "z"
maximize "in-range" $ countInRange (x, y, z)
minimize "distance-from-origin" $ abs x + abs y + abs z
solve =
answer >>= print
现在,这个问题不是真正的 sbv
问题,也不是 Haskell 问题,上面的代码没有任何问题(它解决了 1000-value-puzzle
列表,在我的机器上只需一分钟多一点的时间就可以得到巨大的 X、Y 和 Z 坐标,这对我来说已经足够了)。这个问题是关于在 countInRange
.
中找到的 (0 :: SInteger)
如果我将 (0 :: SInteger)
更改为 (0 :: SInt32)
会导致解决方案花费非常非常长的时间(当我开始输入这个问题时我就开始了,那是 16 分钟前并且还在计算) .
那么,是什么原因呢?为什么 SInt32
在这种情况下要慢得多?是因为我在混合域(在其他地方使用 SInteger
)吗?我原以为无界 SInteger
表示会比有界 Int32
.
慢
请注意,所讨论的符号类型仅用于计算来自 puzzle
的匹配值(因此它将始终 <= 1000,即 puzzle
的长度)。
更新
运行.
40 分钟后,我终止了 Int32
解决方案
当您在 SBV 中编写这样的问题时,性能可以在两个地方发挥作用:
- SBV 可能需要很长时间才能生成查询本身
- SBV 向求解器发送查询正常,但求解器响应时间过长
从你的描述来看,应该是后者;但是你可以通过像这样调用 optimize
来确保是这种情况:
optimizeWith z3{verbose=True} ...
这将执行的操作是打印 SBV 与后端求解器的交互。在某些时候,您会看到:
[SEND] (check-sat)
这意味着 SBV 已经完成了它的工作,现在正在等待求解器返回答案。 运行 您的程序再次打开此选项。如果 SBV 正在慢慢来,那么您 不会 看到上面的 [SEND] (check-sat)
行,并且应该在此处报告为 SBV 问题:https://github.com/LeventErkok/sbv/issues
不过更有可能的是,SBV 正在发送 check-sat
罚款,但是当您使用 SInt32
而不是 SInteger
时,求解器的响应时间要长得多。
假设是这种情况,那么这很可能是因为当基础类型为 SInt32
时,您的问题更难解决。您正在进行大量算术运算,并要求求解器最大化和最小化两个不同的目标。你可以想象,如果你有无限的 Integer
值,最大化数字的加法可能很容易处理:随着参数的增加,它们的总和也会增加。但对于 SInt32
而言并非如此:一旦值开始溢出,由于环绕,总和将远低于参数。因此,与 SInteger
情况相比,使用模块化算法,搜索 space 变得更有趣,规模也更大。底线是,虽然 SInt32
具有有限表示,但与 SInteger x SInteger x SInteger
相比,由于算法的工作原理,SInt32 x SInt32 x SInt32
(你有三个变量)的优化问题可能要困难得多。特别是,SInt32
的解决方案将 而不是 必然与 SInteger
相同,这也是由于模块化算法。
当然,在 z3 内部的门后发生的事情是一个黑盒子,也许它们慢得不合理。如果您认为是这种情况,您也可以向 z3 人员报告。 SBV 可以生成成绩单供您发送给他们,使用时如下所示:
optimizeWith z3{transcript = Just "longRun.smt2"} ...
这将在 SMTLib 符号中创建一个文件 longRun.smt2
,可以将其提供给 Haskell 生态系统之外的求解器。您可以在 https://github.com/Z3Prover/z3/issues 处提交此类错误,但请记住,SBV 生成的 SMTLib 文件可能又长又冗长:如果您可以以某种方式减小问题的大小,但仍能证明该问题,那将会很有帮助。
为了学习 Z3,我尝试使用 Haskell 绑定 sbv
解决我最喜欢的 Advent of Code 问题之一(一个特别困难的问题,2018 day 23, part 2)。前方代码剧透...
module Lib
( solve
) where
import Data.SBV
puzzle :: [((SInteger, SInteger, SInteger), SInteger)]
puzzle = (\((x, y, z), r) -> ((literal x, literal y, literal z), literal r)) <$> [
((60118729,58965711,8716524), 71245377),
{- 999 more values that are large like the first one... -}
]
manhattan (a1, a2, a3) (b1, b2, b3) =
abs (a1 - b1) + abs (a2 - b2) + abs (a3 - b3)
countInRange pos =
foldr (\(nb, r) -> (+) $ oneIf (manhattan nb pos .<= r)) (0 :: SInteger) puzzle
answer = optimize Lexicographic $ do
x <- sInteger "x"
y <- sInteger "y"
z <- sInteger "z"
maximize "in-range" $ countInRange (x, y, z)
minimize "distance-from-origin" $ abs x + abs y + abs z
solve =
answer >>= print
现在,这个问题不是真正的 sbv
问题,也不是 Haskell 问题,上面的代码没有任何问题(它解决了 1000-value-puzzle
列表,在我的机器上只需一分钟多一点的时间就可以得到巨大的 X、Y 和 Z 坐标,这对我来说已经足够了)。这个问题是关于在 countInRange
.
(0 :: SInteger)
如果我将 (0 :: SInteger)
更改为 (0 :: SInt32)
会导致解决方案花费非常非常长的时间(当我开始输入这个问题时我就开始了,那是 16 分钟前并且还在计算) .
那么,是什么原因呢?为什么 SInt32
在这种情况下要慢得多?是因为我在混合域(在其他地方使用 SInteger
)吗?我原以为无界 SInteger
表示会比有界 Int32
.
请注意,所讨论的符号类型仅用于计算来自 puzzle
的匹配值(因此它将始终 <= 1000,即 puzzle
的长度)。
更新 运行.
40 分钟后,我终止了Int32
解决方案
当您在 SBV 中编写这样的问题时,性能可以在两个地方发挥作用:
- SBV 可能需要很长时间才能生成查询本身
- SBV 向求解器发送查询正常,但求解器响应时间过长
从你的描述来看,应该是后者;但是你可以通过像这样调用 optimize
来确保是这种情况:
optimizeWith z3{verbose=True} ...
这将执行的操作是打印 SBV 与后端求解器的交互。在某些时候,您会看到:
[SEND] (check-sat)
这意味着 SBV 已经完成了它的工作,现在正在等待求解器返回答案。 运行 您的程序再次打开此选项。如果 SBV 正在慢慢来,那么您 不会 看到上面的 [SEND] (check-sat)
行,并且应该在此处报告为 SBV 问题:https://github.com/LeventErkok/sbv/issues
不过更有可能的是,SBV 正在发送 check-sat
罚款,但是当您使用 SInt32
而不是 SInteger
时,求解器的响应时间要长得多。
假设是这种情况,那么这很可能是因为当基础类型为 SInt32
时,您的问题更难解决。您正在进行大量算术运算,并要求求解器最大化和最小化两个不同的目标。你可以想象,如果你有无限的 Integer
值,最大化数字的加法可能很容易处理:随着参数的增加,它们的总和也会增加。但对于 SInt32
而言并非如此:一旦值开始溢出,由于环绕,总和将远低于参数。因此,与 SInteger
情况相比,使用模块化算法,搜索 space 变得更有趣,规模也更大。底线是,虽然 SInt32
具有有限表示,但与 SInteger x SInteger x SInteger
相比,由于算法的工作原理,SInt32 x SInt32 x SInt32
(你有三个变量)的优化问题可能要困难得多。特别是,SInt32
的解决方案将 而不是 必然与 SInteger
相同,这也是由于模块化算法。
当然,在 z3 内部的门后发生的事情是一个黑盒子,也许它们慢得不合理。如果您认为是这种情况,您也可以向 z3 人员报告。 SBV 可以生成成绩单供您发送给他们,使用时如下所示:
optimizeWith z3{transcript = Just "longRun.smt2"} ...
这将在 SMTLib 符号中创建一个文件 longRun.smt2
,可以将其提供给 Haskell 生态系统之外的求解器。您可以在 https://github.com/Z3Prover/z3/issues 处提交此类错误,但请记住,SBV 生成的 SMTLib 文件可能又长又冗长:如果您可以以某种方式减小问题的大小,但仍能证明该问题,那将会很有帮助。