Skip to content

Commit 347693f

Browse files
committed
Add GraphDef.empty(), stack-buffer query optimization, and registry test
- Add GraphDef.empty() for creating entry-point empty nodes; replace all no-arg join() calls on GraphDef with empty() in tests. - Optimize _AdjacencySetCore.query() to use a 16-element stack buffer, matching the contains() optimization. - Add test_registry_cleanup exercising destroy(), graph deletion, and weak-reference cleanup of the node registry. Made-with: Cursor
1 parent 15d0036 commit 347693f

File tree

5 files changed

+87
-27
lines changed

5 files changed

+87
-27
lines changed

cuda_core/cuda/core/_graph/_graph_def/_adjacency_set_proxy.pyx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,14 @@ cdef class _AdjacencySetCore:
143143
cdef cydriver.CUgraphNode c_node = as_cu(self._h_node)
144144
if c_node == NULL:
145145
return []
146-
cdef size_t count = 0
146+
cdef cydriver.CUgraphNode buf[16]
147+
cdef size_t count = 16
148+
cdef size_t i
147149
with nogil:
148-
HANDLE_RETURN(self._query_fn(c_node, NULL, &count))
149-
if count == 0:
150-
return []
150+
HANDLE_RETURN(self._query_fn(c_node, buf, &count))
151+
if count <= 16:
152+
return [GraphNode._create(self._h_graph, buf[i])
153+
for i in range(count)]
151154
cdef vector[cydriver.CUgraphNode] nodes_vec
152155
nodes_vec.resize(count)
153156
with nogil:
@@ -166,19 +169,23 @@ cdef class _AdjacencySetCore:
166169
cdef size_t i
167170
with nogil:
168171
HANDLE_RETURN(self._query_fn(c_node, buf, &count))
172+
173+
# Fast path for small sets.
169174
if count <= 16:
170175
for i in range(count):
171176
if buf[i] == target:
172177
return True
173-
else:
174-
cdef vector[cydriver.CUgraphNode] nodes_vec
175-
nodes_vec.resize(count)
176-
with nogil:
177-
HANDLE_RETURN(self._query_fn(c_node, nodes_vec.data(), &count))
178-
assert count == nodes_vec.size()
179-
for i in range(count):
180-
if nodes_vec[i] == target:
181-
return True
178+
return False
179+
180+
# Fallback for large sets.
181+
cdef vector[cydriver.CUgraphNode] nodes_vec
182+
nodes_vec.resize(count)
183+
with nogil:
184+
HANDLE_RETURN(self._query_fn(c_node, nodes_vec.data(), &count))
185+
assert count == nodes_vec.size()
186+
for i in range(count):
187+
if nodes_vec[i] == target:
188+
return True
182189
return False
183190

184191
cdef Py_ssize_t count(self):

cuda_core/cuda/core/_graph/_graph_def/_graph_def.pyx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,16 @@ cdef class GraphDef:
159159
"""
160160
return self._entry.launch(config, kernel, *args)
161161
162+
def empty(self) -> "EmptyNode":
163+
"""Add an entry-point empty node (no dependencies).
164+
165+
Returns
166+
-------
167+
EmptyNode
168+
A new EmptyNode with no dependencies.
169+
"""
170+
return self._entry.join()
171+
162172
def join(self, *nodes) -> "EmptyNode":
163173
"""Create an empty node that depends on all given nodes.
164174

cuda_core/tests/graph/test_graphdef.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ def test_identity_preservation(init_cuda):
703703
"""Round-trips through nodes(), edges(), and pred/succ return extant
704704
objects rather than duplicates."""
705705
g = GraphDef()
706-
a = g.join()
706+
a = g.empty()
707707
b = a.join()
708708

709709
# nodes()
@@ -724,6 +724,49 @@ def test_identity_preservation(init_cuda):
724724
assert b2 is b
725725

726726

727+
def test_registry_cleanup(init_cuda):
728+
"""Node registry entries are removed on destroy() and graph teardown."""
729+
import gc
730+
from cuda.core._graph._graph_def._graph_node import _node_registry
731+
732+
def registered(node):
733+
return any(v is node for v in _node_registry.values())
734+
735+
gc.collect()
736+
assert len(_node_registry) == 0
737+
738+
g = GraphDef()
739+
a = g.empty()
740+
b = g.empty()
741+
c = g.empty()
742+
743+
assert len(_node_registry) == 3
744+
assert registered(a)
745+
assert registered(b)
746+
assert registered(c)
747+
748+
a.destroy()
749+
assert len(_node_registry) == 2
750+
assert not registered(a)
751+
assert registered(b)
752+
assert registered(c)
753+
754+
del g
755+
gc.collect()
756+
assert len(_node_registry) == 2
757+
assert registered(b)
758+
assert registered(c)
759+
760+
b.destroy()
761+
assert len(_node_registry) == 1
762+
assert not registered(b)
763+
assert registered(c)
764+
765+
del c
766+
gc.collect()
767+
assert len(_node_registry) == 0
768+
769+
727770
# =============================================================================
728771
# GraphDef basics
729772
# =============================================================================

cuda_core/tests/graph/test_graphdef_errors.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ def test_condition_from_different_graph(init_cuda):
101101
# =============================================================================
102102

103103

104-
def test_join_no_extra_nodes(init_cuda):
105-
"""join() from entry with no extra nodes creates a single empty node."""
104+
def test_empty_node(init_cuda):
105+
"""empty() creates a single entry-point empty node."""
106106
g = GraphDef()
107-
joined = g.join()
107+
joined = g.empty()
108108
assert isinstance(joined, EmptyNode)
109109
assert len(g.nodes()) == 1
110110

cuda_core/tests/graph/test_graphdef_mutation.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,16 +211,16 @@ def test_insert_b(self, init_cuda):
211211
def test_adjacency_set_interface(init_cuda):
212212
"""Exercise every MutableSet method on AdjacencySetProxy."""
213213
g = GraphDef()
214-
hub = g.join()
215-
items = [g.join() for _ in range(5)]
214+
hub = g.empty()
215+
items = [g.empty() for _ in range(5)]
216216
assert_mutable_set_interface(hub.succ, items)
217217

218218

219219
def test_adjacency_set_pred_direction(init_cuda):
220220
"""Verify that pred works symmetrically with succ."""
221221
g = GraphDef()
222-
target = g.join()
223-
x, y, z = (g.join() for _ in range(3))
222+
target = g.empty()
223+
x, y, z = (g.empty() for _ in range(3))
224224

225225
pred = target.pred
226226
assert pred == set()
@@ -242,8 +242,8 @@ def test_adjacency_set_pred_direction(init_cuda):
242242
def test_adjacency_set_property_setter(init_cuda):
243243
"""Verify that assigning to node.pred or node.succ replaces all edges."""
244244
g = GraphDef()
245-
hub = g.join()
246-
a, b, c = (g.join() for _ in range(3))
245+
hub = g.empty()
246+
a, b, c = (g.empty() for _ in range(3))
247247

248248
hub.succ = {a, b}
249249
assert hub.succ == {a, b}
@@ -310,7 +310,7 @@ def test_destroyed_node(init_cuda):
310310
def test_add_wrong_type(init_cuda):
311311
"""Adding a non-GraphNode raises TypeError."""
312312
g = GraphDef()
313-
node = g.join()
313+
node = g.empty()
314314
with pytest.raises(TypeError, match="expected GraphNode"):
315315
node.succ.add("not a node")
316316
with pytest.raises(TypeError, match="expected GraphNode"):
@@ -321,16 +321,16 @@ def test_cross_graph_edge(init_cuda):
321321
"""Adding an edge to a node from a different graph raises CUDAError."""
322322
g1 = GraphDef()
323323
g2 = GraphDef()
324-
a = g1.join()
325-
b = g2.join()
324+
a = g1.empty()
325+
b = g2.empty()
326326
with pytest.raises(CUDAError):
327327
a.succ.add(b)
328328

329329

330330
def test_self_edge(init_cuda):
331331
"""Adding a self-edge raises CUDAError."""
332332
g = GraphDef()
333-
node = g.join()
333+
node = g.empty()
334334
with pytest.raises(CUDAError):
335335
node.succ.add(node)
336336

0 commit comments

Comments
 (0)