souffle  2.0.2-371-g6315b36
SipsMetric.cpp
Go to the documentation of this file.
1 /*
2  * Souffle - A Datalog Compiler
3  * Copyright (c) 2020, The Souffle Developers. All rights reserved
4  * Licensed under the Universal Permissive License v 1.0 as shown at:
5  * - https://opensource.org/licenses/UPL
6  * - <souffle root>/licenses/SOUFFLE-UPL.txt
7  */
8 
9 /************************************************************************
10  *
11  * @file SipsMetric.cpp
12  *
13  * Defines the SipsMetric class, which specifies cost functions for atom orderings in a clause.
14  *
15  ***********************************************************************/
16 
17 #include "ast/utility/SipsMetric.h"
18 #include "ast/Clause.h"
19 #include "ast/TranslationUnit.h"
20 #include "ast/Variable.h"
21 #include "ast/analysis/IOType.h"
25 #include "ast/utility/Utils.h"
26 #include "ast/utility/Visitor.h"
27 #include <cmath>
28 #include <limits>
29 #include <vector>
30 
31 namespace souffle::ast {
32 
33 std::vector<unsigned int> SipsMetric::getReordering(const Clause* clause) const {
34  BindingStore bindingStore(clause);
35  auto atoms = getBodyLiterals<Atom>(*clause);
36  std::vector<unsigned int> newOrder(atoms.size());
37 
38  size_t numAdded = 0;
39  while (numAdded < atoms.size()) {
40  // grab the index of the next atom, based on the SIPS function
41  const auto& costs = evaluateCosts(atoms, bindingStore);
42  auto minIdx = std::distance(costs.begin(), std::min_element(costs.begin(), costs.end()));
43  const auto* nextAtom = atoms[minIdx];
44  assert(nextAtom != nullptr && "nullptr atoms should have maximal cost");
45 
46  // set all arguments that are variables as bound
47  for (const auto* arg : nextAtom->getArguments()) {
48  if (const auto* var = dynamic_cast<const Variable*>(arg)) {
49  bindingStore.bindVariableStrongly(var->getName());
50  }
51  }
52 
53  newOrder[numAdded] = minIdx; // add to the ordering
54  atoms[minIdx] = nullptr; // mark as done
55  numAdded++; // move on
56  }
57 
58  return newOrder;
59 }
60 
61 /** Create a SIPS metric based on a given heuristic. */
62 std::unique_ptr<SipsMetric> SipsMetric::create(const std::string& heuristic, const TranslationUnit& tu) {
63  if (heuristic == "strict")
64  return std::make_unique<StrictSips>();
65  else if (heuristic == "all-bound")
66  return std::make_unique<AllBoundSips>();
67  else if (heuristic == "naive")
68  return std::make_unique<NaiveSips>();
69  else if (heuristic == "max-bound")
70  return std::make_unique<MaxBoundSips>();
71  else if (heuristic == "max-ratio")
72  return std::make_unique<MaxRatioSips>();
73  else if (heuristic == "least-free")
74  return std::make_unique<LeastFreeSips>();
75  else if (heuristic == "least-free-vars")
76  return std::make_unique<LeastFreeVarsSips>();
77  else if (heuristic == "profile-use")
78  return std::make_unique<ProfileUseSips>(*tu.getAnalysis<analysis::ProfileUseAnalysis>());
79  else if (heuristic == "delta")
80  return std::make_unique<DeltaSips>();
81  else if (heuristic == "input")
82  return std::make_unique<InputSips>(*tu.getAnalysis<analysis::RelationDetailCacheAnalysis>(),
83  *tu.getAnalysis<analysis::IOTypeAnalysis>());
84  else if (heuristic == "delta-input")
85  return std::make_unique<DeltaInputSips>(*tu.getAnalysis<analysis::RelationDetailCacheAnalysis>(),
86  *tu.getAnalysis<analysis::IOTypeAnalysis>());
87 
88  // default is all-bound
89  return create("all-bound", tu);
90 }
91 
92 std::vector<double> StrictSips::evaluateCosts(
93  const std::vector<Atom*> atoms, const BindingStore& /* bindingStore */) const {
94  // Goal: Always choose the left-most atom
95  std::vector<double> cost;
96  for (const auto* atom : atoms) {
97  cost.push_back(atom == nullptr ? std::numeric_limits<double>::max() : 0);
98  }
99  assert(atoms.size() == cost.size() && "each atom should have exactly one cost");
100  return cost;
101 }
102 
103 std::vector<double> AllBoundSips::evaluateCosts(
104  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
105  // Goal: Prioritise atoms with all arguments bound
106  std::vector<double> cost;
107  for (const auto* atom : atoms) {
108  if (atom == nullptr) {
109  cost.push_back(std::numeric_limits<double>::max());
110  continue;
111  }
112 
113  int arity = atom->getArity();
114  int numBound = bindingStore.numBoundArguments(atom);
115  cost.push_back(arity == numBound ? 0 : 1);
116  }
117  assert(atoms.size() == cost.size() && "each atom should have exactly one cost");
118  return cost;
119 }
120 
121 std::vector<double> NaiveSips::evaluateCosts(
122  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
123  // Goal: Prioritise (1) all bound, then (2) atoms with at least one bound argument, then (3) left-most
124  std::vector<double> cost;
125  for (const auto* atom : atoms) {
126  if (atom == nullptr) {
127  cost.push_back(std::numeric_limits<double>::max());
128  continue;
129  }
130 
131  int arity = atom->getArity();
132  int numBound = bindingStore.numBoundArguments(atom);
133  if (arity == numBound) {
134  cost.push_back(0);
135  } else if (numBound >= 1) {
136  cost.push_back(1);
137  } else {
138  cost.push_back(2);
139  }
140  }
141  assert(atoms.size() == cost.size() && "each atom should have exactly one cost");
142  return cost;
143 }
144 
145 std::vector<double> MaxBoundSips::evaluateCosts(
146  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
147  // Goal: prioritise (1) all-bound, then (2) max number of bound vars, then (3) left-most
148  std::vector<double> cost;
149  for (const auto* atom : atoms) {
150  if (atom == nullptr) {
151  cost.push_back(std::numeric_limits<double>::max());
152  continue;
153  }
154 
155  int arity = atom->getArity();
156  int numBound = bindingStore.numBoundArguments(atom);
157  if (arity == numBound) {
158  // Always better than anything else
159  cost.push_back(0);
160  } else if (numBound == 0) {
161  // Always worse than any number of bound vars
162  cost.push_back(2);
163  } else {
164  // Between 0 and 1, decreasing with more num bound
165  cost.push_back(1 / numBound);
166  }
167  }
168  assert(atoms.size() == cost.size() && "each atom should have exactly one cost");
169  return cost;
170 }
171 
172 std::vector<double> MaxRatioSips::evaluateCosts(
173  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
174  // Goal: prioritise max ratio of bound args
175  std::vector<double> cost;
176  for (const auto* atom : atoms) {
177  if (atom == nullptr) {
178  cost.push_back(std::numeric_limits<double>::max());
179  continue;
180  }
181 
182  int arity = atom->getArity();
183  int numBound = bindingStore.numBoundArguments(atom);
184  if (arity == 0) {
185  // Always better than anything else
186  cost.push_back(0);
187  } else if (numBound == 0) {
188  // Always worse than anything else
189  cost.push_back(2);
190  } else {
191  // Between 0 and 1, decreasing as the ratio increases
192  cost.push_back(1 - numBound / arity);
193  }
194  }
195  assert(atoms.size() == cost.size() && "each atom should have exactly one cost");
196  return cost;
197 }
198 
199 std::vector<double> LeastFreeSips::evaluateCosts(
200  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
201  // Goal: choose the atom with the least number of unbound arguments
202  std::vector<double> cost;
203  for (const auto* atom : atoms) {
204  if (atom == nullptr) {
205  cost.push_back(std::numeric_limits<double>::max());
206  continue;
207  }
208 
209  cost.push_back(atom->getArity() - bindingStore.numBoundArguments(atom));
210  }
211  return cost;
212 }
213 
214 std::vector<double> LeastFreeVarsSips::evaluateCosts(
215  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
216  // Goal: choose the atom with the least amount of unbound variables
217  std::vector<double> cost;
218  for (const auto* atom : atoms) {
219  if (atom == nullptr) {
220  cost.push_back(std::numeric_limits<double>::max());
221  continue;
222  }
223 
224  // use a set to hold all free variables to avoid double-counting
225  std::set<std::string> freeVars;
226  visitDepthFirst(*atom, [&](const Variable& var) {
227  if (bindingStore.isBound(var.getName())) {
228  freeVars.insert(var.getName());
229  }
230  });
231  cost.push_back(freeVars.size());
232  }
233  return cost;
234 }
235 
236 std::vector<double> ProfileUseSips::evaluateCosts(
237  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
238  // Goal: reorder based on the given profiling information
239  // Metric: cost(atom_R) = log(|atom_R|) * #free/#args
240  // - exception: propositions are prioritised
241  std::vector<double> cost;
242  for (const auto* atom : atoms) {
243  if (atom == nullptr) {
244  cost.push_back(std::numeric_limits<double>::max());
245  continue;
246  }
247 
248  // prioritise propositions
249  int arity = atom->getArity();
250  if (arity == 0) {
251  cost.push_back(0);
252  continue;
253  }
254 
255  // calculate log(|R|) * #free/#args
256  int numBound = bindingStore.numBoundArguments(atom);
257  int numFree = arity - numBound;
258  double value = log(profileUse.getRelationSize(atom->getQualifiedName()));
259  value *= (numFree * 1.0) / arity;
260  }
261  return cost;
262 }
263 
264 std::vector<double> DeltaSips::evaluateCosts(
265  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
266  // Goal: prioritise (1) all-bound, then (2) deltas, and then (3) left-most
267  std::vector<double> cost;
268  for (const auto* atom : atoms) {
269  if (atom == nullptr) {
270  cost.push_back(std::numeric_limits<double>::max());
271  continue;
272  }
273 
274  int arity = atom->getArity();
275  int numBound = bindingStore.numBoundArguments(atom);
276  if (arity == numBound) {
277  // prioritise all-bound
278  cost.push_back(0);
279  } else if (isDeltaRelation(atom->getQualifiedName())) {
280  // then deltas
281  cost.push_back(1);
282  } else {
283  cost.push_back(2);
284  }
285  }
286  return cost;
287 }
288 
289 std::vector<double> InputSips::evaluateCosts(
290  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
291  // Goal: prioritise (1) all-bound, (2) input, then (3) rest
292  std::vector<double> cost;
293  for (const auto* atom : atoms) {
294  if (atom == nullptr) {
295  cost.push_back(std::numeric_limits<double>::max());
296  continue;
297  }
298 
299  const auto& relName = atom->getQualifiedName();
300  int arity = atom->getArity();
301  int numBound = bindingStore.numBoundArguments(atom);
302  if (arity == numBound) {
303  // prioritise all-bound
304  cost.push_back(0);
305  } else if (ioTypes.isInput(relDetail.getRelation(relName))) {
306  // then input
307  cost.push_back(1);
308  } else {
309  cost.push_back(2);
310  }
311  }
312  return cost;
313 }
314 
315 std::vector<double> DeltaInputSips::evaluateCosts(
316  const std::vector<Atom*> atoms, const BindingStore& bindingStore) const {
317  // Goal: prioritise (1) all-bound, (2) deltas, (3) input, then (4) rest
318  std::vector<double> cost;
319  for (const auto* atom : atoms) {
320  if (atom == nullptr) {
321  cost.push_back(std::numeric_limits<double>::max());
322  continue;
323  }
324 
325  const auto& relName = atom->getQualifiedName();
326  int arity = atom->getArity();
327  int numBound = bindingStore.numBoundArguments(atom);
328  if (arity == numBound) {
329  // prioritise all-bound
330  cost.push_back(0);
331  } else if (isDeltaRelation(relName)) {
332  // then deltas
333  cost.push_back(1);
334  } else if (ioTypes.isInput(relDetail.getRelation(relName))) {
335  // then input
336  cost.push_back(2);
337  } else {
338  cost.push_back(3);
339  }
340  }
341  return cost;
342 }
343 
344 } // namespace souffle::ast
TranslationUnit.h
IOType.h
souffle::ast::analysis::IOTypeAnalysis
Definition: IOType.h:39
souffle::ast::MaxBoundSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:151
souffle::ast::analysis::ProfileUseAnalysis::getRelationSize
size_t getRelationSize(const QualifiedName &rel) const
Return size of relation in the profile.
Definition: ProfileUse.cpp:61
souffle::ast::DeltaSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:270
souffle::ast::InputSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:295
RelationDetailCache.h
souffle::ast::LeastFreeSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:205
ProfileUse.h
souffle::ast::isDeltaRelation
bool isDeltaRelation(const QualifiedName &name)
Returns whether the given atom is a delta relation.
Definition: Utils.cpp:246
souffle::ast::LeastFreeVarsSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:220
souffle::ast::DeltaInputSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:321
souffle::ast::analysis::ProfileUseAnalysis
Analysis that loads profile data and has a profile query interface.
Definition: ProfileUse.h:43
souffle::ast::analysis::RelationDetailCacheAnalysis
Analysis pass mapping identifiers with relations and clauses.
Definition: RelationDetailCache.h:47
souffle::ast::analysis::Variable
A variable to be utilized within constraints to be handled by the constraint solver.
Definition: ConstraintSystem.h:41
souffle::ast::SipsMetric::create
static std::unique_ptr< SipsMetric > create(const std::string &heuristic, const TranslationUnit &tu)
Create a SIPS metric based on a given heuristic.
Definition: SipsMetric.cpp:68
Utils.h
souffle::ast::InputSips::ioTypes
const analysis::IOTypeAnalysis & ioTypes
Definition: SipsMetric.h:172
souffle::ast::DeltaInputSips::relDetail
const analysis::RelationDetailCacheAnalysis & relDetail
Definition: SipsMetric.h:187
souffle::ast::InputSips::relDetail
const analysis::RelationDetailCacheAnalysis & relDetail
Definition: SipsMetric.h:171
souffle::ast::NaiveSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:127
souffle::ast::analysis::IOTypeAnalysis::isInput
bool isInput(const Relation *relation) const
Definition: IOType.h:49
souffle::ast::SipsMetric::evaluateCosts
virtual std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const =0
Evaluates the cost of choosing each atom next in the current schedule.
souffle::ast::AllBoundSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:109
SipsMetric.h
souffle::ast::analysis::RelationDetailCacheAnalysis::getRelation
Relation * getRelation(const QualifiedName &name) const
Definition: RelationDetailCache.h:57
souffle::ast::SipsMetric::getReordering
std::vector< unsigned int > getReordering(const Clause *clause) const
Determines the new ordering of a clause after the SIPS is applied.
Definition: SipsMetric.cpp:39
souffle::ast::MaxRatioSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:178
BindingStore.h
Visitor.h
Clause.h
souffle::ast::StrictSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:98
souffle::ast::visitDepthFirst
void visitDepthFirst(const Node &root, Visitor< R, Ps... > &visitor, Args &... args)
A utility function visiting all nodes within the ast rooted by the given node recursively in a depth-...
Definition: Visitor.h:273
souffle::ast::DeltaInputSips::ioTypes
const analysis::IOTypeAnalysis & ioTypes
Definition: SipsMetric.h:188
souffle::ast
Definition: Aggregator.h:35
souffle::ast::BindingStore
Definition: BindingStore.h:38
Variable.h
souffle::ast::ProfileUseSips::profileUse
const analysis::ProfileUseAnalysis & profileUse
Definition: SipsMetric.h:147
souffle::ast::ProfileUseSips::evaluateCosts
std::vector< double > evaluateCosts(const std::vector< Atom * > atoms, const BindingStore &bindingStore) const override
Evaluates the cost of choosing each atom next in the current schedule.
Definition: SipsMetric.cpp:242