Skip to content

Commit 9241eab

Browse files
committed
perf(graphs): benchmark topological_sort queue performance
1 parent 6750893 commit 9241eab

1 file changed

Lines changed: 59 additions & 25 deletions

File tree

graphs/kahns_algorithm_topo.py

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -65,38 +65,72 @@ def topological_sort(graph: dict[int, list[int]]) -> list[int] | None:
6565
return topo_order # valid topological ordering
6666

6767

68+
def _topological_sort_list_queue(graph: dict[int, list[int]]) -> list[int] | None:
69+
"""
70+
Pre-optimization implementation of Kahn's topological sort using list.pop(0).
71+
72+
Used as a baseline for benchmark comparison against deque.popleft().
73+
"""
74+
indegree = [0] * len(graph)
75+
queue = []
76+
topo_order = []
77+
processed_vertices_count = 0
78+
79+
for values in graph.values():
80+
for i in values:
81+
indegree[i] += 1
82+
83+
for i in range(len(indegree)):
84+
if indegree[i] == 0:
85+
queue.append(i)
86+
87+
while queue:
88+
vertex = queue.pop(0)
89+
processed_vertices_count += 1
90+
topo_order.append(vertex)
91+
92+
for neighbor in graph[vertex]:
93+
indegree[neighbor] -= 1
94+
if indegree[neighbor] == 0:
95+
queue.append(neighbor)
96+
97+
if processed_vertices_count != len(graph):
98+
return None
99+
return topo_order
100+
101+
68102
def benchmark() -> None:
69103
"""
70-
Benchmark comparing list.pop(0) vs collections.deque.popleft().
104+
Benchmark comparing topological_sort() (using deque.popleft) against
105+
the pre-optimization baseline _topological_sort_list_queue() (using list.pop(0)).
71106
72-
Demonstrates the performance difference between O(n) list.pop(0)
73-
and O(1) deque.popleft() operations for Kahn's algorithm queue.
107+
Demonstrates the performance improvement of O(1) queue operations in Kahn's algorithm
108+
on a graph with a large number of zero-indegree vertices.
74109
"""
75110
from timeit import timeit
76111

77-
size = 50_000
78-
runs = 5
112+
num_sources = 30_000
113+
graph = {i: [num_sources] for i in range(num_sources)}
114+
graph[num_sources] = []
115+
116+
# Verify correctness: both implementations produce valid topological sorts
117+
old_result = _topological_sort_list_queue(graph)
118+
new_result = topological_sort(graph)
119+
assert old_result is not None and new_result is not None
120+
assert len(old_result) == len(new_result) == num_sources + 1
121+
assert set(old_result) == set(new_result)
79122

80-
def use_list() -> None:
81-
queue = list(range(size))
82-
while queue:
83-
queue.pop(0)
84-
85-
def use_deque() -> None:
86-
queue = deque(range(size))
87-
while queue:
88-
queue.popleft()
89-
90-
list_time = timeit(use_list, number=runs)
91-
deque_time = timeit(use_deque, number=runs)
92-
93-
print(f"Benchmark results for queue size of {size} over {runs} runs:")
94-
print(f"list.pop(0): {list_time:.5f} seconds")
95-
print(f"deque.popleft(): {deque_time:.5f} seconds")
96-
if deque_time > 0:
97-
print(
98-
f"deque.popleft() is {list_time / deque_time:.2f}x faster than list.pop(0)"
99-
)
123+
runs = 5
124+
old_time = timeit(lambda: _topological_sort_list_queue(graph), number=runs)
125+
new_time = timeit(lambda: topological_sort(graph), number=runs)
126+
127+
print(
128+
f"Benchmark results for topological_sort with {num_sources} vertices over {runs} runs:"
129+
)
130+
print(f"Pre-optimization (list.pop(0)): {old_time:.5f} seconds")
131+
print(f"Current (deque.popleft): {new_time:.5f} seconds")
132+
if new_time > 0:
133+
print(f"Speedup ratio: {old_time / new_time:.2f}x faster")
100134

101135

102136
if __name__ == "__main__":

0 commit comments

Comments
 (0)