From d95b86e6e2353a74e1b7bef1dc3a3951f26d8faf Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 15 May 2026 19:50:00 -0700 Subject: [PATCH 1/3] done --- submissions/kylemumma.py | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 submissions/kylemumma.py diff --git a/submissions/kylemumma.py b/submissions/kylemumma.py new file mode 100644 index 0000000..2f3164b --- /dev/null +++ b/submissions/kylemumma.py @@ -0,0 +1,86 @@ +"""Build graph simulator challenge — submission template. + +Implement build_all() to build all targets in the graph as fast as possible. + +Rules: +- You must call target.build() for each target — do not skip or replace it. +- Every target must be built exactly once. +- A target must not be built until all of its dependencies have completed. +""" +from graph import BuildGraph, Target +import pdb +from concurrent.futures import Future, ThreadPoolExecutor, wait, FIRST_COMPLETED +from logging import getLogger, DEBUG + +logger = getLogger(__name__) +logger.disabled = True + + +def build_all(graph: BuildGraph) -> dict[str, bytes]: + """Build all targets in the graph, respecting dependency order. + + Args: + graph: The build graph to execute. + + Returns: + A dict mapping target name to its build result (bytes). + """ + # TODO: implement your build scheduler here + # future_to_node = {} + visited: set[str] = set() + todo: list[tuple[Target, dict]] = [] + dependents: dict[str, set[str]] = {} + # name -> (waiting_on, results) + dep_results: dict[str, tuple[int, dict[str, bytes]]] = {} + for name, target in graph.targets.items(): + assert name == target.name + if name not in visited: + dfs(target, visited, dependents, todo, dep_results) + + latest = "" + with ThreadPoolExecutor() as executor: + # e[0] is the target + for e in todo: + logger.warning(f"submitting {e[0].name}") + future_to_node: dict[Future[bytes], str] = {} + pending = set() + for target_and_dep_res in todo: + future = executor.submit(target_and_dep_res[0].build, target_and_dep_res[1]) + future_to_node[future] = target_and_dep_res[0].name + pending.add(future) + while pending: + done, pending = wait(pending, return_when=FIRST_COMPLETED) + for future in done: + name = future_to_node[future] + logger.warning(f"done {name}") + latest = name + res = future.result() + dependant = dependents.get(name, []) + for child in dependant: + dep_results[child][1][name] = res + dep_results[child] = (dep_results[child][0]-1, dep_results[child][1]) + if dep_results[child][0] == 0: + target=graph.targets[child] + #dep_results = {} + #for d in target.deps: + # dep_results[d.name] = results[d.name] + logger.warning(f"submitting {target.name} with {dep_results[child][1]}") + new_future = executor.submit(target.build, dep_results[child][1]) + future_to_node[new_future] = target.name + pending.add(new_future) + return dep_results[latest][1] + +def dfs(node: Target, visited: set[str], dependents: dict[str, set[str]], todo: list[tuple], dep_results: dict[str, tuple[int, dict[str, bytes]]]): + if node.name in visited: + return + visited.add(node.name) + # build the depended -> dependent graph + dep_results[node.name] = (len(node.deps), {}) + for child in node.deps: + if child.name not in dependents: + dependents[child.name] = set() + dependents[child.name].add(node.name) + dfs(child, visited, dependents, todo, dep_results) + if len(node.deps) == 0: + todo.append((node, {})) + From 42372338245d99c0a3d1203a06ed73032833d004 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 15 May 2026 20:57:51 -0700 Subject: [PATCH 2/3] max workers --- submissions/kylemumma.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/submissions/kylemumma.py b/submissions/kylemumma.py index 2f3164b..2e14bdb 100644 --- a/submissions/kylemumma.py +++ b/submissions/kylemumma.py @@ -8,13 +8,7 @@ - A target must not be built until all of its dependencies have completed. """ from graph import BuildGraph, Target -import pdb from concurrent.futures import Future, ThreadPoolExecutor, wait, FIRST_COMPLETED -from logging import getLogger, DEBUG - -logger = getLogger(__name__) -logger.disabled = True - def build_all(graph: BuildGraph) -> dict[str, bytes]: """Build all targets in the graph, respecting dependency order. @@ -38,10 +32,8 @@ def build_all(graph: BuildGraph) -> dict[str, bytes]: dfs(target, visited, dependents, todo, dep_results) latest = "" - with ThreadPoolExecutor() as executor: + with ThreadPoolExecutor(max_workers=24) as executor: # e[0] is the target - for e in todo: - logger.warning(f"submitting {e[0].name}") future_to_node: dict[Future[bytes], str] = {} pending = set() for target_and_dep_res in todo: @@ -52,7 +44,6 @@ def build_all(graph: BuildGraph) -> dict[str, bytes]: done, pending = wait(pending, return_when=FIRST_COMPLETED) for future in done: name = future_to_node[future] - logger.warning(f"done {name}") latest = name res = future.result() dependant = dependents.get(name, []) @@ -61,10 +52,6 @@ def build_all(graph: BuildGraph) -> dict[str, bytes]: dep_results[child] = (dep_results[child][0]-1, dep_results[child][1]) if dep_results[child][0] == 0: target=graph.targets[child] - #dep_results = {} - #for d in target.deps: - # dep_results[d.name] = results[d.name] - logger.warning(f"submitting {target.name} with {dep_results[child][1]}") new_future = executor.submit(target.build, dep_results[child][1]) future_to_node[new_future] = target.name pending.add(new_future) From b17ada1e8ee8a36bbe7908dc051d44df8b226699 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 16 May 2026 15:41:05 -0700 Subject: [PATCH 3/3] shared memory --- submissions/kylemumma.py | 71 +++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/submissions/kylemumma.py b/submissions/kylemumma.py index 2e14bdb..e8b4a8d 100644 --- a/submissions/kylemumma.py +++ b/submissions/kylemumma.py @@ -9,6 +9,8 @@ """ from graph import BuildGraph, Target from concurrent.futures import Future, ThreadPoolExecutor, wait, FIRST_COMPLETED +from queue import SimpleQueue +from threading import Lock def build_all(graph: BuildGraph) -> dict[str, bytes]: """Build all targets in the graph, respecting dependency order. @@ -22,7 +24,7 @@ def build_all(graph: BuildGraph) -> dict[str, bytes]: # TODO: implement your build scheduler here # future_to_node = {} visited: set[str] = set() - todo: list[tuple[Target, dict]] = [] + todo: list[Target] = [] dependents: dict[str, set[str]] = {} # name -> (waiting_on, results) dep_results: dict[str, tuple[int, dict[str, bytes]]] = {} @@ -31,33 +33,48 @@ def build_all(graph: BuildGraph) -> dict[str, bytes]: if name not in visited: dfs(target, visited, dependents, todo, dep_results) - latest = "" + # find the # of roots + root_cnt = len(graph.targets) - len(dependents) + + ready = SimpleQueue() + lock = Lock() + for e in todo: + ready.put(e) + + def worker(target: Target): + nonlocal root_cnt + # build + arg = dep_results[target.name][1] if target.name in dep_results else {} + res = target.build(arg) + + # update state + lock.acquire() + if target.name not in dependents: + # no dependers (in_deg), this is the root + root_cnt -= 1 + if root_cnt == 0: + ready.put((None, target.name)) + lock.release() + return + for d in dependents.get(target.name, []): + dep_results[d][1][target.name] = res + dep_results[d] = (dep_results[d][0]-1, dep_results[d][1]) + if dep_results[d][0] == 0: + ready.put(graph.targets[d]) + lock.release() + with ThreadPoolExecutor(max_workers=24) as executor: - # e[0] is the target - future_to_node: dict[Future[bytes], str] = {} - pending = set() - for target_and_dep_res in todo: - future = executor.submit(target_and_dep_res[0].build, target_and_dep_res[1]) - future_to_node[future] = target_and_dep_res[0].name - pending.add(future) - while pending: - done, pending = wait(pending, return_when=FIRST_COMPLETED) - for future in done: - name = future_to_node[future] - latest = name - res = future.result() - dependant = dependents.get(name, []) - for child in dependant: - dep_results[child][1][name] = res - dep_results[child] = (dep_results[child][0]-1, dep_results[child][1]) - if dep_results[child][0] == 0: - target=graph.targets[child] - new_future = executor.submit(target.build, dep_results[child][1]) - future_to_node[new_future] = target.name - pending.add(new_future) - return dep_results[latest][1] + while True: + # submit new work + target = ready.get() + if isinstance(target, tuple): + assert target[0] is None + return dep_results[target[1]][1] + executor.submit(worker, target) + + return None -def dfs(node: Target, visited: set[str], dependents: dict[str, set[str]], todo: list[tuple], dep_results: dict[str, tuple[int, dict[str, bytes]]]): +def dfs(node: Target, visited: set[str], dependents: dict[str, set[str]], todo: list[Target], dep_results: dict[str, tuple[int, dict[str, bytes]]]): if node.name in visited: return visited.add(node.name) @@ -69,5 +86,5 @@ def dfs(node: Target, visited: set[str], dependents: dict[str, set[str]], todo: dependents[child.name].add(node.name) dfs(child, visited, dependents, todo, dep_results) if len(node.deps) == 0: - todo.append((node, {})) + todo.append(node)