现在大多数编程语言都提供了list,set,multiset,map等容器。对容器的所有元素的操作,例如copy_if
或transform
通常需要O(n)时间。如果你只需要结果的前几个元素,那么延迟评估可以使这个次线性,但如果你需要完整的结果,它会回到线性。
考虑例如DPLL算法的命题可满足性的以下实现。它本质上是将伪代码转换为C ++,因此针对可读性进行了优化。但是每个步骤在变量数量上都需要时间线性,因此即使它第一次能够正确地猜测所有分配,总时间和内存消耗在变量数量上也是二次的,这使得实现在实践中使用太慢。即使我们应用众所周知的优化,例如通过常量引用传递容器,并在任何可能避免不必要工作的地方使用延迟评估,也是如此。
DPLL的高效实现使用增量技术,其中不是扫描和复制整个容器,而是使用每步的O(1)时间和内存来计算进行小变化(如分配单个变量)的后果。
人类可以将伪代码或未优化的参考实现转换为有效的增量实现;这就是我们在编写实用的SAT求解器,定理证明器等时所做的工作。我们付出的代价是从此开始使用大量复杂的优化代码,这比使用简洁的数学逻辑描述困难得多。
我们理想的是一个更高级别的编译器,它可以将未优化的参考实现编译成有效的增量代码。
我的问题是:有没有做过这方面的工作?是否有任何现有的实施,部分实施或讨论?容器操作的更高级优化原则上并不完全是未知的,例如, SQL查询优化器,Haskell循环融合,但我不知道有什么试图在我这里寻找。
DPLL的未优化参考实现(伪代码的简单转换):
bool dpll(map<Var, bool> m, set<set<Literal>> clauses) {
clauses = eval(m, clauses);
// Solved
if (isFalse(clauses))
return false;
if (isTrue(clauses))
return true;
// Unit clause
auto unitClauses =
copy_if(clauses, [](set<Literal> clause) { return clause.size() == 1; });
if (unitClauses.size()) {
auto x = front(front(unitClauses));
return dpll(m + makeMap(x.var, x.pol), clauses);
}
// Pure literal
auto pureVars =
copy_if(vars(clauses), [=](Var x) { return pure(x, clauses); });
if (pureVars.size()) {
auto x = front(pureVars);
return dpll(m + makeMap(x, pol(x, clauses)), clauses);
}
// Choice
auto x = choose(vars(clauses));
return dpll(m + makeMap(x, false), clauses) ||
dpll(m + makeMap(x, true), clauses);
}
辅助函数的类似参考实现:https://github.com/russellw/ayane/blob/master/logic/dpll.cpp