1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 // GraphCycles provides incremental cycle detection on a dynamic
17 // graph using the following algorithm:
18 //
19 // A dynamic topological sort algorithm for directed acyclic graphs
20 // David J. Pearce, Paul H. J. Kelly
21 // Journal of Experimental Algorithmics (JEA) JEA Homepage archive
22 // Volume 11, 2006, Article No. 1.7
23 //
24 // Brief summary of the algorithm:
25 //
26 // (1) Maintain a rank for each node that is consistent
27 // with the topological sort of the graph. I.e., path from x to y
28 // implies rank[x] < rank[y].
29 // (2) When a new edge (x->y) is inserted, do nothing if rank[x] < rank[y].
30 // (3) Otherwise: adjust ranks in the neighborhood of x and y.
31
32 #include "tensorflow/compiler/jit/graphcycles/graphcycles.h"
33
34 #include <algorithm>
35 #include <unordered_set>
36
37 #include "absl/container/inlined_vector.h"
38 #include "tensorflow/core/platform/logging.h"
39
40 namespace tensorflow {
41
42 namespace {
43
44 typedef std::unordered_set<int32> NodeSet;
45 template <typename T>
46 struct VecStruct {
47 typedef absl::InlinedVector<T, 4> type;
48 };
49 template <typename T>
50 using Vec = typename VecStruct<T>::type;
51
52 struct Node {
Nodetensorflow::__anon0349984b0111::Node53 Node() : in(4), out(4) {} // Small hashtables for in/out edges
54
55 int32 rank; // rank number assigned by Pearce-Kelly algorithm
56 bool visited; // Temporary marker used by depth-first-search
57 void* data; // User-supplied data
58 NodeSet in; // List of immediate predecessor nodes in graph
59 NodeSet out; // List of immediate successor nodes in graph
60 };
61
62 } // namespace
63
64 struct GraphCycles::Rep {
65 Vec<Node*> nodes_;
66 Vec<int32> free_nodes_; // Indices for unused entries in nodes_
67
68 // Temporary state.
69 Vec<int32> deltaf_; // Results of forward DFS
70 Vec<int32> deltab_; // Results of backward DFS
71 Vec<int32> list_; // All nodes to reprocess
72 Vec<int32> merged_; // Rank values to assign to list_ entries
73 Vec<int32> stack_; // Emulates recursion stack when doing depth first search
74 };
75
GraphCycles()76 GraphCycles::GraphCycles() : rep_(new Rep) {}
77
~GraphCycles()78 GraphCycles::~GraphCycles() {
79 for (Vec<Node*>::size_type i = 0; i < rep_->nodes_.size(); i++) {
80 delete rep_->nodes_[i];
81 }
82 delete rep_;
83 }
84
CheckInvariants() const85 bool GraphCycles::CheckInvariants() const {
86 Rep* r = rep_;
87 NodeSet ranks; // Set of ranks seen so far.
88 for (Vec<Node*>::size_type x = 0; x < r->nodes_.size(); x++) {
89 Node* nx = r->nodes_[x];
90 if (nx->visited) {
91 LOG(FATAL) << "Did not clear visited marker on node " << x;
92 }
93 if (!ranks.insert(nx->rank).second) {
94 LOG(FATAL) << "Duplicate occurrence of rank " << nx->rank;
95 }
96 for (auto y : nx->out) {
97 Node* ny = r->nodes_[y];
98 if (nx->rank >= ny->rank) {
99 LOG(FATAL) << "Edge " << x << "->" << y << " has bad rank assignment "
100 << nx->rank << "->" << ny->rank;
101 }
102 }
103 }
104 return true;
105 }
106
NewNode()107 int32 GraphCycles::NewNode() {
108 if (rep_->free_nodes_.empty()) {
109 Node* n = new Node;
110 n->visited = false;
111 n->data = nullptr;
112 n->rank = rep_->nodes_.size();
113 rep_->nodes_.push_back(n);
114 return n->rank;
115 } else {
116 // Preserve preceding rank since the set of ranks in use must be
117 // a permutation of [0,rep_->nodes_.size()-1].
118 int32 r = rep_->free_nodes_.back();
119 rep_->nodes_[r]->data = nullptr;
120 rep_->free_nodes_.pop_back();
121 return r;
122 }
123 }
124
RemoveNode(int32 node)125 void GraphCycles::RemoveNode(int32 node) {
126 Node* x = rep_->nodes_[node];
127 for (auto y : x->out) {
128 rep_->nodes_[y]->in.erase(node);
129 }
130 for (auto y : x->in) {
131 rep_->nodes_[y]->out.erase(node);
132 }
133 x->in.clear();
134 x->out.clear();
135 rep_->free_nodes_.push_back(node);
136 }
137
GetNodeData(int32 node) const138 void* GraphCycles::GetNodeData(int32 node) const {
139 return rep_->nodes_[node]->data;
140 }
141
SetNodeData(int32 node,void * data)142 void GraphCycles::SetNodeData(int32 node, void* data) {
143 rep_->nodes_[node]->data = data;
144 }
145
HasEdge(int32 x,int32 y) const146 bool GraphCycles::HasEdge(int32 x, int32 y) const {
147 return rep_->nodes_[x]->out.find(y) != rep_->nodes_[x]->out.end();
148 }
149
RemoveEdge(int32 x,int32 y)150 void GraphCycles::RemoveEdge(int32 x, int32 y) {
151 rep_->nodes_[x]->out.erase(y);
152 rep_->nodes_[y]->in.erase(x);
153 // No need to update the rank assignment since a previous valid
154 // rank assignment remains valid after an edge deletion.
155 }
156
157 static bool ForwardDFS(GraphCycles::Rep* r, int32 n, int32 upper_bound);
158 static void BackwardDFS(GraphCycles::Rep* r, int32 n, int32 lower_bound);
159 static void Reorder(GraphCycles::Rep* r);
160 static void Sort(const Vec<Node*>&, Vec<int32>* delta);
161 static void MoveToList(GraphCycles::Rep* r, Vec<int32>* src, Vec<int32>* dst);
162 static void ClearVisitedBits(GraphCycles::Rep* r, const Vec<int32>& nodes);
163
InsertEdge(int32 x,int32 y)164 bool GraphCycles::InsertEdge(int32 x, int32 y) {
165 if (x == y) return false;
166 Rep* r = rep_;
167 Node* nx = r->nodes_[x];
168 if (!nx->out.insert(y).second) {
169 // Edge already exists.
170 return true;
171 }
172
173 Node* ny = r->nodes_[y];
174 ny->in.insert(x);
175
176 if (nx->rank <= ny->rank) {
177 // New edge is consistent with existing rank assignment.
178 return true;
179 }
180
181 // Current rank assignments are incompatible with the new edge. Recompute.
182 // We only need to consider nodes that fall in the range [ny->rank,nx->rank].
183 if (!ForwardDFS(r, y, nx->rank)) {
184 // Found a cycle. Undo the insertion and tell caller.
185 nx->out.erase(y);
186 ny->in.erase(x);
187 // Since we do not call Reorder() on this path, clear any visited
188 // markers left by ForwardDFS.
189 ClearVisitedBits(r, r->deltaf_);
190 return false;
191 }
192 BackwardDFS(r, x, ny->rank);
193 Reorder(r);
194 return true;
195 }
196
ForwardDFS(GraphCycles::Rep * r,int32 n,int32 upper_bound)197 static bool ForwardDFS(GraphCycles::Rep* r, int32 n, int32 upper_bound) {
198 // Avoid recursion since stack space might be limited.
199 // We instead keep a stack of nodes to visit.
200 r->deltaf_.clear();
201 r->stack_.clear();
202 r->stack_.push_back(n);
203 while (!r->stack_.empty()) {
204 n = r->stack_.back();
205 r->stack_.pop_back();
206 Node* nn = r->nodes_[n];
207 if (nn->visited) continue;
208
209 nn->visited = true;
210 r->deltaf_.push_back(n);
211
212 for (auto w : nn->out) {
213 Node* nw = r->nodes_[w];
214 if (nw->rank == upper_bound) {
215 return false; // Cycle
216 }
217 if (!nw->visited && nw->rank < upper_bound) {
218 r->stack_.push_back(w);
219 }
220 }
221 }
222 return true;
223 }
224
BackwardDFS(GraphCycles::Rep * r,int32 n,int32 lower_bound)225 static void BackwardDFS(GraphCycles::Rep* r, int32 n, int32 lower_bound) {
226 r->deltab_.clear();
227 r->stack_.clear();
228 r->stack_.push_back(n);
229 while (!r->stack_.empty()) {
230 n = r->stack_.back();
231 r->stack_.pop_back();
232 Node* nn = r->nodes_[n];
233 if (nn->visited) continue;
234
235 nn->visited = true;
236 r->deltab_.push_back(n);
237
238 for (auto w : nn->in) {
239 Node* nw = r->nodes_[w];
240 if (!nw->visited && lower_bound < nw->rank) {
241 r->stack_.push_back(w);
242 }
243 }
244 }
245 }
246
Reorder(GraphCycles::Rep * r)247 static void Reorder(GraphCycles::Rep* r) {
248 Sort(r->nodes_, &r->deltab_);
249 Sort(r->nodes_, &r->deltaf_);
250
251 // Adds contents of delta lists to list_ (backwards deltas first).
252 r->list_.clear();
253 MoveToList(r, &r->deltab_, &r->list_);
254 MoveToList(r, &r->deltaf_, &r->list_);
255
256 // Produce sorted list of all ranks that will be reassigned.
257 r->merged_.resize(r->deltab_.size() + r->deltaf_.size());
258 std::merge(r->deltab_.begin(), r->deltab_.end(), r->deltaf_.begin(),
259 r->deltaf_.end(), r->merged_.begin());
260
261 // Assign the ranks in order to the collected list.
262 for (Vec<int32>::size_type i = 0; i < r->list_.size(); i++) {
263 r->nodes_[r->list_[i]]->rank = r->merged_[i];
264 }
265 }
266
Sort(const Vec<Node * > & nodes,Vec<int32> * delta)267 static void Sort(const Vec<Node*>& nodes, Vec<int32>* delta) {
268 struct ByRank {
269 const Vec<Node*>* nodes;
270 bool operator()(int32 a, int32 b) const {
271 return (*nodes)[a]->rank < (*nodes)[b]->rank;
272 }
273 };
274 ByRank cmp;
275 cmp.nodes = &nodes;
276 std::sort(delta->begin(), delta->end(), cmp);
277 }
278
MoveToList(GraphCycles::Rep * r,Vec<int32> * src,Vec<int32> * dst)279 static void MoveToList(GraphCycles::Rep* r, Vec<int32>* src, Vec<int32>* dst) {
280 for (Vec<int32>::size_type i = 0; i < src->size(); i++) {
281 int32 w = (*src)[i];
282 (*src)[i] = r->nodes_[w]->rank; // Replace src entry with its rank
283 r->nodes_[w]->visited = false; // Prepare for future DFS calls
284 dst->push_back(w);
285 }
286 }
287
ClearVisitedBits(GraphCycles::Rep * r,const Vec<int32> & nodes)288 static void ClearVisitedBits(GraphCycles::Rep* r, const Vec<int32>& nodes) {
289 for (Vec<int32>::size_type i = 0; i < nodes.size(); i++) {
290 r->nodes_[nodes[i]]->visited = false;
291 }
292 }
293
FindPath(int32 x,int32 y,int max_path_len,int32 path[]) const294 int GraphCycles::FindPath(int32 x, int32 y, int max_path_len,
295 int32 path[]) const {
296 // Forward depth first search starting at x until we hit y.
297 // As we descend into a node, we push it onto the path.
298 // As we leave a node, we remove it from the path.
299 int path_len = 0;
300
301 Rep* r = rep_;
302 NodeSet seen;
303 r->stack_.clear();
304 r->stack_.push_back(x);
305 while (!r->stack_.empty()) {
306 int32 n = r->stack_.back();
307 r->stack_.pop_back();
308 if (n < 0) {
309 // Marker to indicate that we are leaving a node
310 path_len--;
311 continue;
312 }
313
314 if (path_len < max_path_len) {
315 path[path_len] = n;
316 }
317 path_len++;
318 r->stack_.push_back(-1); // Will remove tentative path entry
319
320 if (n == y) {
321 return path_len;
322 }
323
324 for (auto w : r->nodes_[n]->out) {
325 if (seen.insert(w).second) {
326 r->stack_.push_back(w);
327 }
328 }
329 }
330
331 return 0;
332 }
333
IsReachable(int32 x,int32 y) const334 bool GraphCycles::IsReachable(int32 x, int32 y) const {
335 return FindPath(x, y, 0, nullptr) > 0;
336 }
337
IsReachableNonConst(int32 x,int32 y)338 bool GraphCycles::IsReachableNonConst(int32 x, int32 y) {
339 if (x == y) return true;
340 Rep* r = rep_;
341 Node* nx = r->nodes_[x];
342 Node* ny = r->nodes_[y];
343
344 if (nx->rank >= ny->rank) {
345 // x cannot reach y since it is after it in the topological ordering
346 return false;
347 }
348
349 // See if x can reach y using a DFS search that is limited to y's rank
350 bool reachable = !ForwardDFS(r, x, ny->rank);
351
352 // Clear any visited markers left by ForwardDFS.
353 ClearVisitedBits(r, r->deltaf_);
354 return reachable;
355 }
356
CanContractEdge(int32 a,int32 b)357 bool GraphCycles::CanContractEdge(int32 a, int32 b) {
358 CHECK(HasEdge(a, b)) << "No edge exists from " << a << " to " << b;
359 RemoveEdge(a, b);
360 bool reachable = IsReachableNonConst(a, b);
361 // Restore the graph to its original state.
362 InsertEdge(a, b);
363 // If reachable, then contracting edge will cause cycle.
364 return !reachable;
365 }
366
ContractEdge(int32 a,int32 b)367 bool GraphCycles::ContractEdge(int32 a, int32 b) {
368 CHECK(HasEdge(a, b));
369 RemoveEdge(a, b);
370
371 if (IsReachableNonConst(a, b)) {
372 // Restore the graph to its original state.
373 InsertEdge(a, b);
374 return false;
375 }
376
377 Node* nb = rep_->nodes_[b];
378 std::unordered_set<int32> out = std::move(nb->out);
379 std::unordered_set<int32> in = std::move(nb->in);
380 for (auto y : out) {
381 rep_->nodes_[y]->in.erase(b);
382 }
383 for (auto y : in) {
384 rep_->nodes_[y]->out.erase(b);
385 }
386 rep_->free_nodes_.push_back(b);
387
388 for (auto y : out) {
389 InsertEdge(a, y);
390 }
391 for (auto y : in) {
392 InsertEdge(y, a);
393 }
394 return true;
395 }
396
Successors(int32 node)397 std::unordered_set<int32> GraphCycles::Successors(int32 node) {
398 return rep_->nodes_[node]->out;
399 }
400
Predecessors(int32 node)401 std::unordered_set<int32> GraphCycles::Predecessors(int32 node) {
402 return rep_->nodes_[node]->in;
403 }
404
405 } // namespace tensorflow
406