diff --git a/doc/reference.rst b/doc/reference.rst index a1cb0efab031476bd469cbacea8b7112603e37d9..f2dad9056d2085213af6458c8eb2aac9b9fa78ca 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -374,6 +374,8 @@ following always works:: .. autofunction:: get_dot_dependency_graph +.. autofunction:: show_dependency_graph + Flags ----- diff --git a/loopy/__init__.py b/loopy/__init__.py index e1819f1f994969f29b545a0272016867bcc48f9b..301fae4d0c113c6e063b10cd4412233cf4221b8a 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -57,6 +57,7 @@ from loopy.kernel.data import ( from loopy.kernel import LoopKernel from loopy.kernel.tools import ( get_dot_dependency_graph, + show_dependency_graph, add_dtypes, add_and_infer_dtypes) from loopy.kernel.creation import make_kernel, UniqueName @@ -95,7 +96,9 @@ __all__ = [ "precompute", "split_arg_axis", "find_padding_multiple", "add_padding", - "get_dot_dependency_graph", "add_dtypes", + "get_dot_dependency_graph", + "show_dependency_graph", + "add_dtypes", "infer_argument_dtypes", "add_and_infer_dtypes", "preprocess_kernel", "realize_reduction", "infer_unknown_types", diff --git a/loopy/kernel/__init__.py b/loopy/kernel/__init__.py index 1ced7f2d66705467db7514fbbbb333ec49708680..a07d1b6107110b039e4ccd9a0605a9b51cfd0101 100644 --- a/loopy/kernel/__init__.py +++ b/loopy/kernel/__init__.py @@ -908,7 +908,8 @@ class LoopKernel(Record): dep_lines.append("%s : %s" % (insn.id, ",".join(insn.insn_deps))) if dep_lines: lines.append(sep) - lines.append("DEPENDENCIES:") + lines.append("DEPENDENCIES: " + "(use loopy.show_dependency_graph to visualize)") lines.extend(dep_lines) lines.append(sep) diff --git a/loopy/kernel/tools.py b/loopy/kernel/tools.py index 57ca8890c29a3736da6db49905ebc6894a333504..b875cf117ba98ecf3e1af4d7ba1dbcec05822527 100644 --- a/loopy/kernel/tools.py +++ b/loopy/kernel/tools.py @@ -27,6 +27,7 @@ THE SOFTWARE. import numpy as np from islpy import dim_type +from loopy.diagnostic import LoopyError import logging logger = logging.getLogger(__name__) @@ -324,25 +325,106 @@ class DomainChanger: # {{{ graphviz / dot export -def get_dot_dependency_graph(kernel, iname_cluster=False, iname_edge=True): +def get_dot_dependency_graph(kernel, iname_cluster=True, use_insn_id=False): + """Return a string in the `dot <http://graphviz.org/>`_ language depicting + dependencies among kernel instructions. + """ + + # make sure all automatically added stuff shows up + from loopy import preprocess_kernel + kernel = preprocess_kernel(kernel) + + if iname_cluster and not kernel.schedule: + from loopy.schedule import get_one_scheduled_kernel + kernel = get_one_scheduled_kernel(kernel) + + dep_graph = {} lines = [] for insn in kernel.instructions: - lines.append("%s [shape=\"box\"];" % insn.id) + if use_insn_id: + insn_label = insn.id + else: + insn_label = "%s <- %s" % (insn.assignee, insn.expression) + + lines.append("\"%s\" [label=\"%s\",shape=\"box\",tooltip=\"%s\"];" + % (insn.id, repr(insn_label)[1:-1], insn.id)) for dep in insn.insn_deps: - lines.append("%s -> %s;" % (dep, insn.id)) + dep_graph.setdefault(insn.id, set()).add(dep) + + # {{{ O(n^3) transitive reduction + + # first, compute transitive closure by fixed point iteration + while True: + changed_something = False + + for insn_1 in dep_graph: + for insn_2 in dep_graph.get(insn_1, set()).copy(): + for insn_3 in dep_graph.get(insn_2, set()).copy(): + if insn_3 not in dep_graph.get(insn_1, set()): + changed_something = True + dep_graph[insn_1].add(insn_3) + + if not changed_something: + break - if iname_edge: - for iname in kernel.insn_inames(insn): - lines.append("%s -> %s [style=\"dotted\"];" % (iname, insn.id)) + for insn_1 in dep_graph: + for insn_2 in dep_graph.get(insn_1, set()).copy(): + for insn_3 in dep_graph.get(insn_2, set()).copy(): + if insn_3 in dep_graph.get(insn_1, set()): + dep_graph[insn_1].remove(insn_3) + + # }}} + + for insn_1 in dep_graph: + for insn_2 in dep_graph.get(insn_1, set()): + lines.append("%s -> %s" % (insn_2, insn_1)) if iname_cluster: - for iname in kernel.all_inames(): - lines.append("subgraph cluster_%s { label=\"%s\" %s }" % (iname, iname, - " ".join(insn.id for insn in kernel.instructions - if iname in kernel.insn_inames(insn)))) + from loopy.schedule import EnterLoop, LeaveLoop, RunInstruction, Barrier + + for sched_item in kernel.schedule: + if isinstance(sched_item, EnterLoop): + lines.append("subgraph cluster_%s { label=\"%s\"" + % (sched_item.iname, sched_item.iname)) + elif isinstance(sched_item, LeaveLoop): + lines.append("}") + elif isinstance(sched_item, RunInstruction): + lines.append(sched_item.insn_id) + elif isinstance(sched_item, Barrier): + pass + else: + raise LoopyError("schedule item not unterstood: %r" % sched_item) return "digraph loopy_deps {\n%s\n}" % "\n".join(lines) + +def show_dependency_graph(*args, **kwargs): + """Show the dependency graph generated by :func:`get_dot_dependency_graph` + in a browser. Accepts the same arguments as that function. + """ + + dot = get_dot_dependency_graph(*args, **kwargs) + + from tempfile import mkdtemp + temp_dir = mkdtemp(prefix="tmp_loppy_dot") + + dot_file_name = "loopy.dot" + + from os.path import join + with open(join(temp_dir, dot_file_name), "w") as dotf: + dotf.write(dot) + + svg_file_name = "loopy.svg" + from subprocess import check_call + check_call(["dot", "-Tsvg", "-o", svg_file_name, dot_file_name], + cwd=temp_dir) + + full_svg_file_name = join(temp_dir, svg_file_name) + logger.info("show_dot_dependency_graph: svg written to '%s'") + + from webbrowser import open as browser_open + browser_open("file://" + full_svg_file_name) + # }}}