问题描述
可满足性问题研究的是取值为布尔值的析取子式的合取范式组成的表达式是否为TRUE的问题。要解决这个问题,也就是使得整个表达式为TRUE,你必须首先确定一组取值为TRUE或者FALSE的布尔变量是否存在。三元可满足性(3SAT)是可满足性问题的一种,它限制了析取子式只能有3个变量。以下是3SAT表达式的一个例子:
(X_1 | !X_2 | X_3) & (X_3 | X_2 | !X_1) & (X_2 | X_1 | !X_3) & (!X_1 | !X_2 | !X_3) “|”表示逻辑“或”(OR), “&”表示逻辑“与”(AND),“!”表示逻辑“非”(NOT)。当X_1和X_2为TRUE, X_3为FALSE时,此表达式为TRUE。
写一个线程程序,首先从一个文本文件中导入3SAT问题表达式实例,然后确定是否存在使得给定表达式为TRUE的布尔值赋值。
如果存在这样一组赋值,应该把结果打印到标准输出设备,同时将布尔变量的赋值输出到一个文本文件。如果赋值不存在,输出文件会被省略。输入和输出文件名应为应用程序执行命令行的第一和第二个参数。
文件格式:输入文件的第一行将包含两个整数:表达式中布尔变量的最大数量(这里用N表示)和文件中析取子式的数量(这里用K表示)。接下来的K行将包含ABS( [ 1 ,N] )中三个整数,三个整数之间以一个空格隔开。这些整数代表布尔变量的下标,负值表示对子式中布尔变量取反。
如果存在这样一组赋值,满足整个输入的表达式的值为TRUE,那么输出文件将包含N行。每一行表示N个变量中的第几个变量以及所赋的布尔值。每行格式: [ 1 ,N]中的一个整数,一个空格,表示TRUE或FALSE的字符 “T” 或 “F”。
输入文件样例(如上述例子)
3 4
1 -2 3
3 2 -1
2 1 -3
-1 -2 -3
输出文件样例(如上述例子)
2 T
3 F
1 T
计时:总执行时间将用于计分
串行算法
可满足性问题属于NP问题,可以使用局部搜索算法(GWSAT,WALKSAT)和全局搜索算法(DPLL),局部搜索算法对于有解的问题,通常可以快速的找到解,但不能确定问题是否无解。全局搜索算法能够确定问题无解,但速度要远远低于局部搜索算法。
WALKSAT局部搜索的算法:
1, 随机的对每个变量赋值,统计未满足的表达式和已满足的表达式为真的子条件个数。
2, 选择一个变量。
选择方法有很多种,比如从未满足的表达式中随机选择一个表达式,从这个表达式中选择一个翻转后导致未满足的表达式变为满足的数量最大的。(优化的算法使用了随机策略,一定几率的任意返回这个表达式的任意子变量)。还有一些算法参考walksat + tabu,novelty,rnovelty。
3, 翻转该变量,更新未满足的表达式和已满足的表达式为真的子条件个数。
4, 如果所有表达式都已满足,输出解,否则回到2。
设定局部搜索算法变量翻转次数N,如果翻转了N次依然没有找到解,就认为该问题无解,N越大,将有解问题判定为无解的几率越小,但绝对不是零。
一种全局搜索算法:
1, 选择一个未赋值变量,该变量关联的未满足表达式最多。
2, 对该变量赋值T,
3, 如果变量赋值T,所有未满表达式都有未赋值的变量
A. 如果所有表达式都已满足,输出解,否则递归调用1.
4, 对该变量赋值F,
5, 如果变量赋值F,所有未满表达式都有未赋值的变量
B. 如果所有表达式都已满足,输出解,否则递归调用1.
由于不确定是否允许使用局部搜索算法,所以我同时实现了这两种算法,其中局部搜索算法根据Henry Kautz<kautz@cs.washington.edu>的程序walksat改编。效率非常高,能瞬间求出有250个变量的有解问题的解。全局搜索为自行设计的算法实现,跟成熟的DPLL算法相比有很大差距,目前求解75个变量的问题大概在10秒左右。
并行算法
对于局部搜索算法,可以开启多个线程各自搜索,一旦搜索到解,所有线程退出。使用tbb::parallel_for可以很容易的实现,见CX3SATLocalSearchOperator类。
对于全局搜索算法,可以在递归调用环节进行任务分解,分别处理赋值T和赋值F。使用tbb::task实现,见CX3SATWholeSearchTask类。
优化工具
以下给出局部搜索并行算法的性能优化检测结果。
Hotspots检测
使用Intel Amplifier的Hotspots检测功能查找热点函数,结果如下:
主要的时间开销都在函数翻转变量X3SATAtomFlip_Local及选择变量X3SATPickBest_Local的函数内,优化这两个函数能得到最大的性能提升。
Concurrency检测
使用Intel Amplifier的Concurrency检测功能查找可进行并行优化的代码,结果如下:
代码主要的函数X3SATAtomFlip_Local及X3SATPickBest_Local都有很高的并行度。
Locks and Waits检测
使用Intel Amplifier的Locks and Waits检测功能查找锁和同步等待消耗,结果如下:
结果显示存在23ms的同步消耗,不存在较严重的同步和锁消耗。
性能测试
操作系统: 32bit的测试在32位XP下完成。
64bit的测试在64位XP下完成。
CPU: Intel(R) Core(TM)2 CPU 5270 @ 1.40GHz
内存: 1G
时间单位: 秒
局部搜索算法结果:
测试数据 | 32bit串行 | 32bit并行 | 加速比 | 64bit串行 | 64bit并行 | 加速比 |
uf50-218 | 0.008363 | 0.007737 | 1.09 | 0.010860 | 0.011945 | 0.91 |
uf75-325 | 0.007995 | 0.007788 | 1.02 | 0.010906 | 0.010992 | 1.00 |
uf250-1065 | 0.009309 | 0.008380 | 1.12 | 0.013284 | 0.015172 | 0.87 |
uuf50-218 | 0.319345 | 0.195871 | 1.63 | 0.377701 | 0.252893 | 1.49 |
uuf75-325 | 0.528795 | 0.319043 | 1.66 | 0.566366 | 0.380140 | 1.49 |
uuf250-1065 | 8.406969 | 4.981784 | 1.68 | 8.097578 | 4.849143 | 1.67 |
全局搜索算法结果:
测试数据 | 32bit串行 | 32bit并行 | 加速比 | 64bit串行 | 64bit并行 | 加速比 |
uf50-218 | 0.018955 | 0.021946 | 0.86 | 0.037525 | 0.039605 | 0.95 |
uf75-325 | 10.975312 | 6.074871 | 1.80 | 11.240384 | 6.231125 | 1.80 |
uuf50-218 | 0.079563 | 0.048530 | 1.63 | 0.112661 | 0.103112 | 1.09 |
uuf75-325 | 11.109457 | 6.467592 | 1.72 | 11.205552 | 6.554428 | 1.71 |
编译说明
Windows平台:
使用VS2008和Intel Parallel Studio
1. 用VS2008打开本项目.
2. 选择X64平台Relase编译.
3. 进入Bin目录执行文件为X3SAT.exe.
Linux平台:
使用ICC和TBB
1. 上传压缩包种的Src和Linux两个目录到服务器上.
2. 进入X3SAT/Linux目录 执行make
3. 进入X3SAT/Bin目录 执行文件为X3SAT.
其他:
请使用Win下的64bit Relase版本进行测试。
优化结论
通过这道竞赛题,我熟悉了3SAT问题的完全搜索算法和局部搜索算法,也通过blackcoffee(最后一支烟)的文章了解了这个问题从完全搜索,到引用N皇后问题的算法,采用局部搜索。然后是GSAT->GSAT+TABU->GWSAT ->WalkSAT-> WalkSAT+TABU
->NOVELTY和R-NOVELTY 的一步步发展。
由于时间关系,只实现了WalkSAT,也尝试了一下NOVELTY,效果没有想象中那么明显,最后还是回到了WalkSAT。
完全搜索算法,我没有太多的去了解,是自己摸索出来的算法,刚开始是按顺序对每个变量赋值,然后检查是否有子句不可能满足,如果有就进行剪枝。再深入一步,优先考虑关联表达式较多的变量可以将剪枝提前,从而缩小搜索范围。于是在加载的时候统计每个变量的关联的表达式数量,数量多的先赋值,效率提高不少。最后就是参考walksat,每一次都选择关联的不满足的表达式数量最多的变量进行赋值,就是最终的提交版本了。
值得一提的是测试结论,可以局部搜索算法在处理有解问题的时候,加速比很小,有时候比串行代码还要慢,原因在于题目只要求找一个解,而局部搜索算法又有随机性,所以串行代码在数据量CPU核很少的时候比并行算法还快。CPU的核越多,问题的变量越多,并行算法比串行算法的几率就越大。
致谢
感谢Clay Breshears所做的解答,感谢Henry Kautz开源的walksat-dist帮组我完成了3SAT问题的编码,感谢blackcoffee(最后一支烟)在瀚海星云发表的《一个经典问题的算法发展史》帮助我了解3SAT问题的发展历史,并最终选择了walksat算法进行并行改进。