mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
dvc: optimize building graph (#3443)
Use trie structure to efficiently check out, dep and stage paths being prefixes of each other.
This commit is contained in:
+41
-32
@@ -1,12 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from dvc.ignore import CleanTree
|
from dvc.ignore import CleanTree
|
||||||
from dvc.compat import fspath_py35
|
from dvc.compat import fspath_py35
|
||||||
|
|
||||||
from funcy import cached_property, cat
|
from funcy import cached_property, cat, first
|
||||||
|
|
||||||
from dvc.config import Config
|
from dvc.config import Config
|
||||||
from dvc.exceptions import (
|
from dvc.exceptions import (
|
||||||
@@ -305,6 +304,7 @@ class Repo(object):
|
|||||||
CyclicGraphError: resulting graph has cycles
|
CyclicGraphError: resulting graph has cycles
|
||||||
"""
|
"""
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
from pygtrie import Trie
|
||||||
from dvc.exceptions import (
|
from dvc.exceptions import (
|
||||||
OutputDuplicationError,
|
OutputDuplicationError,
|
||||||
StagePathAsOutputError,
|
StagePathAsOutputError,
|
||||||
@@ -314,48 +314,57 @@ class Repo(object):
|
|||||||
G = nx.DiGraph()
|
G = nx.DiGraph()
|
||||||
stages = stages or self.stages
|
stages = stages or self.stages
|
||||||
stages = [stage for stage in stages if stage]
|
stages = [stage for stage in stages if stage]
|
||||||
outs = {}
|
outs = Trie() # Use trie to efficiently find overlapping outs and deps
|
||||||
|
|
||||||
for stage in stages:
|
for stage in stages:
|
||||||
for out in stage.outs:
|
for out in stage.outs:
|
||||||
if out.path_info in outs:
|
out_key = out.path_info.parts
|
||||||
dup_stages = [stage, outs[out.path_info].stage]
|
|
||||||
|
# Check for dup outs
|
||||||
|
if out_key in outs:
|
||||||
|
dup_stages = [stage, outs[out_key].stage]
|
||||||
raise OutputDuplicationError(str(out), dup_stages)
|
raise OutputDuplicationError(str(out), dup_stages)
|
||||||
outs[out.path_info] = out
|
|
||||||
|
# Check for overlapping outs
|
||||||
|
if outs.has_subtrie(out_key):
|
||||||
|
parent = out
|
||||||
|
overlapping = first(outs.values(prefix=out_key))
|
||||||
|
else:
|
||||||
|
parent = outs.shortest_prefix(out_key).value
|
||||||
|
overlapping = out
|
||||||
|
if parent and overlapping:
|
||||||
|
msg = (
|
||||||
|
"Paths for outs:\n'{}'('{}')\n'{}'('{}')\n"
|
||||||
|
"overlap. To avoid unpredictable behaviour, "
|
||||||
|
"rerun command with non overlapping outs paths."
|
||||||
|
).format(
|
||||||
|
str(parent),
|
||||||
|
parent.stage.relpath,
|
||||||
|
str(overlapping),
|
||||||
|
overlapping.stage.relpath,
|
||||||
|
)
|
||||||
|
raise OverlappingOutputPathsError(parent, overlapping, msg)
|
||||||
|
|
||||||
|
outs[out_key] = out
|
||||||
|
|
||||||
for stage in stages:
|
for stage in stages:
|
||||||
for out in stage.outs:
|
out = outs.shortest_prefix(PathInfo(stage.path).parts).value
|
||||||
for p in out.path_info.parents:
|
if out:
|
||||||
if p in outs:
|
raise StagePathAsOutputError(stage, str(out))
|
||||||
msg = (
|
|
||||||
"Paths for outs:\n'{}'('{}')\n'{}'('{}')\n"
|
|
||||||
"overlap. To avoid unpredictable behaviour, "
|
|
||||||
"rerun command with non overlapping outs paths."
|
|
||||||
).format(
|
|
||||||
str(outs[p]),
|
|
||||||
outs[p].stage.relpath,
|
|
||||||
str(out),
|
|
||||||
out.stage.relpath,
|
|
||||||
)
|
|
||||||
raise OverlappingOutputPathsError(outs[p], out, msg)
|
|
||||||
|
|
||||||
|
# Building graph
|
||||||
|
G.add_nodes_from(stages)
|
||||||
for stage in stages:
|
for stage in stages:
|
||||||
stage_path_info = PathInfo(stage.path)
|
|
||||||
for p in chain([stage_path_info], stage_path_info.parents):
|
|
||||||
if p in outs:
|
|
||||||
raise StagePathAsOutputError(stage, str(outs[p]))
|
|
||||||
|
|
||||||
for stage in stages:
|
|
||||||
G.add_node(stage)
|
|
||||||
|
|
||||||
for dep in stage.deps:
|
for dep in stage.deps:
|
||||||
if dep.path_info is None:
|
if dep.path_info is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for out_path_info, out in outs.items():
|
dep_key = dep.path_info.parts
|
||||||
if out_path_info.overlaps(dep.path_info):
|
overlapping = list(n.value for n in outs.prefixes(dep_key))
|
||||||
G.add_node(out.stage)
|
if outs.has_subtrie(dep_key):
|
||||||
G.add_edge(stage, out.stage)
|
overlapping.extend(outs.values(prefix=dep_key))
|
||||||
|
|
||||||
|
G.add_edges_from((stage, out.stage) for out in overlapping)
|
||||||
|
|
||||||
check_acyclic(G)
|
check_acyclic(G)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user