From 28df996617df9c7db43ec7070d87597d30fd9d57 Mon Sep 17 00:00:00 2001 From: pleroy Date: Mon, 30 Dec 2024 19:14:17 +0100 Subject: [PATCH 1/7] Moar colors. --- osaca/semantics/kernel_dg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osaca/semantics/kernel_dg.py b/osaca/semantics/kernel_dg.py index 7d3eb46..8c312bd 100644 --- a/osaca/semantics/kernel_dg.py +++ b/osaca/semantics/kernel_dg.py @@ -521,8 +521,8 @@ class KernelDG(nx.DiGraph): for dep in lcd: lcd_line_numbers[dep] = [x.line_number for x, lat in lcd[dep]["dependencies"]] # add color scheme - graph.graph["node"] = {"colorscheme": "accent8"} - graph.graph["edge"] = {"colorscheme": "accent8"} + graph.graph["node"] = {"colorscheme": "set312"} + graph.graph["edge"] = {"colorscheme": "set312"} # create LCD edges for dep in lcd_line_numbers: From 7d900fde38e618b2d74f53f280b5f96ef548da66 Mon Sep 17 00:00:00 2001 From: Robin Leroy Date: Mon, 30 Dec 2024 21:48:15 +0100 Subject: [PATCH 2/7] =?UTF-8?q?Don=E2=80=99t=20run=20out=20of=20colours?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- osaca/semantics/kernel_dg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osaca/semantics/kernel_dg.py b/osaca/semantics/kernel_dg.py index 8c312bd..efde5a4 100644 --- a/osaca/semantics/kernel_dg.py +++ b/osaca/semantics/kernel_dg.py @@ -553,7 +553,7 @@ class KernelDG(nx.DiGraph): graph.nodes[n]["style"] = "filled" else: graph.nodes[n]["style"] += ",filled" - graph.nodes[n]["fillcolor"] = 2 + col + graph.nodes[n]["fillcolor"] = 2 + col % 11 # color edges for e in graph.edges: From e096cf470472409419109d975d44d9070060303d Mon Sep 17 00:00:00 2001 From: Robin Leroy Date: Wed, 1 Jan 2025 03:55:51 +0100 Subject: [PATCH 3/7] =?UTF-8?q?Don=E2=80=99t=20spam=20filled=20until=20dot?= =?UTF-8?q?=20breaks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- osaca/semantics/kernel_dg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osaca/semantics/kernel_dg.py b/osaca/semantics/kernel_dg.py index efde5a4..5332a3e 100644 --- a/osaca/semantics/kernel_dg.py +++ b/osaca/semantics/kernel_dg.py @@ -551,7 +551,7 @@ class KernelDG(nx.DiGraph): if n in lcd_line_numbers[dep]: if "style" not in graph.nodes[n]: graph.nodes[n]["style"] = "filled" - else: + elif ",filled" not in graph.nodes[n]["style"]: graph.nodes[n]["style"] += ",filled" graph.nodes[n]["fillcolor"] = 2 + col % 11 From 8c31c6ff77d8c02f056ad8c21173f078797e9f82 Mon Sep 17 00:00:00 2001 From: Robin Leroy Date: Wed, 1 Jan 2025 04:03:09 +0100 Subject: [PATCH 4/7] Mark backward edges as backward so the graph is ordered like the code --- osaca/semantics/kernel_dg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osaca/semantics/kernel_dg.py b/osaca/semantics/kernel_dg.py index 5332a3e..79904ac 100644 --- a/osaca/semantics/kernel_dg.py +++ b/osaca/semantics/kernel_dg.py @@ -528,8 +528,8 @@ class KernelDG(nx.DiGraph): for dep in lcd_line_numbers: min_line_number = min(lcd_line_numbers[dep]) max_line_number = max(lcd_line_numbers[dep]) - graph.add_edge(max_line_number, min_line_number) - graph.edges[max_line_number, min_line_number]["latency"] = [ + graph.add_edge(min_line_number, max_line_number, dir="back") + graph.edges[min_line_number, max_line_number]["latency"] = [ lat for x, lat in lcd[dep]["dependencies"] if x.line_number == max_line_number ] From b854562a82630800f57005f50a3c33e26468776e Mon Sep 17 00:00:00 2001 From: Robin Leroy Date: Wed, 1 Jan 2025 18:34:28 +0100 Subject: [PATCH 5/7] Improve dependency graph colouring --- osaca/semantics/kernel_dg.py | 98 +++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/osaca/semantics/kernel_dg.py b/osaca/semantics/kernel_dg.py index 79904ac..318bb8d 100644 --- a/osaca/semantics/kernel_dg.py +++ b/osaca/semantics/kernel_dg.py @@ -521,8 +521,10 @@ class KernelDG(nx.DiGraph): for dep in lcd: lcd_line_numbers[dep] = [x.line_number for x, lat in lcd[dep]["dependencies"]] # add color scheme - graph.graph["node"] = {"colorscheme": "set312"} - graph.graph["edge"] = {"colorscheme": "set312"} + graph.graph["node"] = {"colorscheme": "spectral9"} + graph.graph["edge"] = {"colorscheme": "spectral9"} + min_color = 2 + available_colors = 8 # create LCD edges for dep in lcd_line_numbers: @@ -541,21 +543,14 @@ class KernelDG(nx.DiGraph): for n in cp: graph.nodes[n.line_number]["instruction_form"].latency_cp = n.latency_cp - # color CP and LCD + # Make the critical path bold. for n in graph.nodes: if n in cp_line_numbers: # graph.nodes[n]['color'] = 1 graph.nodes[n]["style"] = "bold" graph.nodes[n]["penwidth"] = 4 - for col, dep in enumerate(lcd): - if n in lcd_line_numbers[dep]: - if "style" not in graph.nodes[n]: - graph.nodes[n]["style"] = "filled" - elif ",filled" not in graph.nodes[n]["style"]: - graph.nodes[n]["style"] += ",filled" - graph.nodes[n]["fillcolor"] = 2 + col % 11 - # color edges + # Make critical path edges bold. for e in graph.edges: if ( graph.nodes[e[0]]["instruction_form"].line_number in cp_line_numbers @@ -569,12 +564,81 @@ class KernelDG(nx.DiGraph): if bold_edge: graph.edges[e]["style"] = "bold" graph.edges[e]["penwidth"] = 3 - for dep in lcd_line_numbers: - if ( - graph.nodes[e[0]]["instruction_form"].line_number in lcd_line_numbers[dep] - and graph.nodes[e[1]]["instruction_form"].line_number in lcd_line_numbers[dep] - ): - graph.edges[e]["color"] = graph.nodes[e[1]]["fillcolor"] + + # Color the cycles created by loop-carried dependencies, longest first, never recoloring + # any node, so that the longest LCD and most long chains that are involved in the loop are + # legible. + for i, dep in enumerate(sorted(lcd, key=lambda dep: -lcd[dep]["latency"])): + # For cycles that are broken by already-colored (longer) cycles, the color need not be + # the same for each yet-uncolored arc. + # Do not use the same color for such an arc as for the cycles that delimit it. This is + # always possible with 3 colors, as each arc is only adjacent to the preceding and + # following interrupting cycles. + # Since we color edges as well as nodes, there would be room for a more interesting + # graph coloring problem: we could avoid having unrelated arcs with the same color + # meeting at the same vertex, and retain the same color between arcs of the same cycle + # that are interrupted by a single vertex. We mostly ignore this problem. + + # The longest cycle will always have color 1, the second longest cycle will always have + # color 2 except where it overlaps with with the longest cycle, etc.; for arcs that are + # part of short cycles, the colors will be less predictable. + default_color = min_color + i % available_colors + arc = [] + arc_source = lcd_line_numbers[dep][-1] + arcs = [] + for n in lcd_line_numbers[dep]: + if "fillcolor" in graph.nodes[n]: + arcs.append((arc, (arc_source, n))) + arc = [] + arc_source = n + else: + arc.append(n) + if not arcs: # Unconstrained cycle. + arcs.append((arc, tuple())) + else: + arcs.append((arc, (arc_source, lcd_line_numbers[dep][0]))) + # Try to color the whole cycle with its default color, then with a single color, then + # with different colors by arc, preferring the default. + forbidden_colors = set( + graph.nodes[n]["fillcolor"] for arc, extremities in arcs for n in extremities + if "fillcolor" in graph.nodes[n] + ) + global_color = None + if default_color not in forbidden_colors: + global_color = default_color + elif len(forbidden_colors) < available_colors: + global_color = next( + c for c in range(min_color, min_color + available_colors + 1) + if c not in forbidden_colors + ) + for arc, extremities in arcs: + if global_color: + color = global_color + else: + color = default_color + while color in (graph.nodes[n].get("fillcolor") for n in extremities): + color = min_color + (color + 1) % available_colors + for n in arc: + if "style" not in graph.nodes[n]: + graph.nodes[n]["style"] = "filled" + else: + graph.nodes[n]["style"] += ",filled" + graph.nodes[n]["fillcolor"] = color + if extremities: + (source, sink) = extremities + else: + source = sink = arc[0] + arc = arc[1:] + for u, v in zip([source] + arc, arc + [sink]): + # The backward edge of the cycle is represented as the corresponding forward + # edge with the attribute dir=back. + edge = graph.edges[v, u] if (v, u) in graph.edges else graph.edges[u, v] + if arc: + if "color" in edge: + raise AssertionError( + f"Recoloring {u}->{v} in arc ({source}) {arc} ({sink}) of {dep}" + ) + edge["color"] = color # rename node from [idx] to [idx mnemonic] and add shape mapping = {} From d82bc8052b56ecb3adb98247f5c4acefd178262e Mon Sep 17 00:00:00 2001 From: Robin Leroy Date: Wed, 1 Jan 2025 23:13:52 +0100 Subject: [PATCH 6/7] Less clever and more useful colouring --- osaca/semantics/kernel_dg.py | 123 +++++++++++++---------------------- 1 file changed, 45 insertions(+), 78 deletions(-) diff --git a/osaca/semantics/kernel_dg.py b/osaca/semantics/kernel_dg.py index 318bb8d..416ecb7 100644 --- a/osaca/semantics/kernel_dg.py +++ b/osaca/semantics/kernel_dg.py @@ -2,7 +2,7 @@ import copy import time -from itertools import chain +from itertools import chain, groupby from multiprocessing import Manager, Process, cpu_count import networkx as nx @@ -520,11 +520,6 @@ class KernelDG(nx.DiGraph): lcd_line_numbers = {} for dep in lcd: lcd_line_numbers[dep] = [x.line_number for x, lat in lcd[dep]["dependencies"]] - # add color scheme - graph.graph["node"] = {"colorscheme": "spectral9"} - graph.graph["edge"] = {"colorscheme": "spectral9"} - min_color = 2 - available_colors = 8 # create LCD edges for dep in lcd_line_numbers: @@ -566,79 +561,51 @@ class KernelDG(nx.DiGraph): graph.edges[e]["penwidth"] = 3 # Color the cycles created by loop-carried dependencies, longest first, never recoloring - # any node, so that the longest LCD and most long chains that are involved in the loop are - # legible. - for i, dep in enumerate(sorted(lcd, key=lambda dep: -lcd[dep]["latency"])): - # For cycles that are broken by already-colored (longer) cycles, the color need not be - # the same for each yet-uncolored arc. - # Do not use the same color for such an arc as for the cycles that delimit it. This is - # always possible with 3 colors, as each arc is only adjacent to the preceding and - # following interrupting cycles. - # Since we color edges as well as nodes, there would be room for a more interesting - # graph coloring problem: we could avoid having unrelated arcs with the same color - # meeting at the same vertex, and retain the same color between arcs of the same cycle - # that are interrupted by a single vertex. We mostly ignore this problem. - - # The longest cycle will always have color 1, the second longest cycle will always have - # color 2 except where it overlaps with with the longest cycle, etc.; for arcs that are - # part of short cycles, the colors will be less predictable. - default_color = min_color + i % available_colors - arc = [] - arc_source = lcd_line_numbers[dep][-1] - arcs = [] - for n in lcd_line_numbers[dep]: - if "fillcolor" in graph.nodes[n]: - arcs.append((arc, (arc_source, n))) - arc = [] - arc_source = n - else: - arc.append(n) - if not arcs: # Unconstrained cycle. - arcs.append((arc, tuple())) + # any node or edge, so that the longest LCD and most long chains that are involved in the + # loop are legible. + lcd_by_latencies = sorted( + ( + (latency, list(deps)) + for latency, deps in groupby(lcd, lambda dep: lcd[dep]["latency"]) + ), + reverse=True + ) + node_colors = {} + edge_colors = {} + colors_used = 0 + for i, (latency, deps) in enumerate(lcd_by_latencies): + color = None + for dep in deps: + path = lcd_line_numbers[dep] + for n in path: + if n not in node_colors: + if not color: + color = colors_used + 1 + colors_used += 1 + node_colors[n] = color + for u, v in zip(path, path[1:] + [path[0]]): + if (u, v) not in edge_colors: + # Don’t introduce a color just for an edge. + if not color: + color = colors_used + edge_colors[u, v] = color + max_color = min(11, colors_used) + colorscheme = f"spectral{max(3, max_color)}" + graph.graph["node"] = {"colorscheme" : colorscheme} + graph.graph["edge"] = {"colorscheme" : colorscheme} + for n, color in node_colors.items(): + if "style" not in graph.nodes[n]: + graph.nodes[n]["style"] = "filled" else: - arcs.append((arc, (arc_source, lcd_line_numbers[dep][0]))) - # Try to color the whole cycle with its default color, then with a single color, then - # with different colors by arc, preferring the default. - forbidden_colors = set( - graph.nodes[n]["fillcolor"] for arc, extremities in arcs for n in extremities - if "fillcolor" in graph.nodes[n] - ) - global_color = None - if default_color not in forbidden_colors: - global_color = default_color - elif len(forbidden_colors) < available_colors: - global_color = next( - c for c in range(min_color, min_color + available_colors + 1) - if c not in forbidden_colors - ) - for arc, extremities in arcs: - if global_color: - color = global_color - else: - color = default_color - while color in (graph.nodes[n].get("fillcolor") for n in extremities): - color = min_color + (color + 1) % available_colors - for n in arc: - if "style" not in graph.nodes[n]: - graph.nodes[n]["style"] = "filled" - else: - graph.nodes[n]["style"] += ",filled" - graph.nodes[n]["fillcolor"] = color - if extremities: - (source, sink) = extremities - else: - source = sink = arc[0] - arc = arc[1:] - for u, v in zip([source] + arc, arc + [sink]): - # The backward edge of the cycle is represented as the corresponding forward - # edge with the attribute dir=back. - edge = graph.edges[v, u] if (v, u) in graph.edges else graph.edges[u, v] - if arc: - if "color" in edge: - raise AssertionError( - f"Recoloring {u}->{v} in arc ({source}) {arc} ({sink}) of {dep}" - ) - edge["color"] = color + graph.nodes[n]["style"] += ",filled" + graph.nodes[n]["fillcolor"] = color + if (max_color >= 4 and color == 1) or (max_color >= 10 and color in (1, 2, max_color)): + graph.nodes[n]["fontcolor"] = "white" + for (u, v), color in edge_colors.items(): + # The backward edge of the cycle is represented as the corresponding forward + # edge with the attribute dir=back. + edge = graph.edges[u, v] if (u, v) in graph.edges else graph.edges[v, u] + edge["color"] = color # rename node from [idx] to [idx mnemonic] and add shape mapping = {} From de0b1fde642dd6df44e7ee2a8dcd53d1d41b3601 Mon Sep 17 00:00:00 2001 From: Robin Leroy Date: Thu, 2 Jan 2025 03:06:14 +0100 Subject: [PATCH 7/7] white on blue --- osaca/semantics/kernel_dg.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osaca/semantics/kernel_dg.py b/osaca/semantics/kernel_dg.py index 416ecb7..2dd46fb 100644 --- a/osaca/semantics/kernel_dg.py +++ b/osaca/semantics/kernel_dg.py @@ -599,7 +599,10 @@ class KernelDG(nx.DiGraph): else: graph.nodes[n]["style"] += ",filled" graph.nodes[n]["fillcolor"] = color - if (max_color >= 4 and color == 1) or (max_color >= 10 and color in (1, 2, max_color)): + if ( + (max_color >= 4 and color in (1, max_color)) or + (max_color >= 10 and color in (1, 2, max_color - 1 , max_color)) + ): graph.nodes[n]["fontcolor"] = "white" for (u, v), color in edge_colors.items(): # The backward edge of the cycle is represented as the corresponding forward