Skip to content

Commit 84fb200

Browse files
authored
perf(py): mutable Node to avoid linear update cost (#2288)
`list.index` call to update parent was very costly for nodes with many children. This was only necessary because Node was frozen, this can be avoided with a custom hash.
1 parent 9ef05f6 commit 84fb200

File tree

3 files changed

+12
-12
lines changed

3 files changed

+12
-12
lines changed

hugr-py/src/hugr/build/dfg.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from contextlib import AbstractContextManager
6-
from dataclasses import dataclass, field, replace
6+
from dataclasses import dataclass, field
77
from typing import (
88
TYPE_CHECKING,
99
Any,
@@ -230,8 +230,8 @@ def add_op(
230230
"""
231231
new_n = self.hugr.add_node(op, self.parent_node, metadata=metadata)
232232
self._wire_up(new_n, args)
233-
234-
return replace(new_n, _num_out_ports=op.num_out)
233+
new_n._num_out_ports = op.num_out
234+
return new_n
235235

236236
def add(self, com: ops.Command, *, metadata: dict[str, Any] | None = None) -> Node:
237237
"""Add a command (holding a dataflow operation and the incoming wires)

hugr-py/src/hugr/hugr/base.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import json
66
from collections.abc import Iterable, Iterator, Mapping
7-
from dataclasses import dataclass, field, replace
7+
from dataclasses import dataclass, field
88
from queue import Queue
99
from typing import (
1010
TYPE_CHECKING,
@@ -290,7 +290,8 @@ def _add_node(
290290
else:
291291
node = Node(len(self._nodes), {})
292292
self._nodes.append(node_data)
293-
node = replace(node, _num_out_ports=num_outs, _metadata=node_data.metadata)
293+
node._num_out_ports = num_outs
294+
node._metadata = node_data.metadata
294295
if parent:
295296
self[parent].children.append(node)
296297

@@ -322,11 +323,7 @@ def _update_port_count(
322323
self[node]._num_inps = num_inps
323324
if num_outs is not None:
324325
self[node]._num_outs = num_outs
325-
node = replace(node, _num_out_ports=num_outs)
326-
parent = self[node].parent
327-
if parent is not None:
328-
pos = self[parent].children.index(node)
329-
self[parent].children[pos] = node
326+
node._num_out_ports = num_outs
330327

331328
if node.idx == self.entrypoint.idx:
332329
self.entrypoint = node
@@ -412,7 +409,7 @@ def delete_node(self, node: ToNode) -> NodeData | None:
412409
weight, self._nodes[node.idx] = self._nodes[node.idx], None
413410

414411
# Free up the metadata dictionary
415-
node = replace(node, _metadata={})
412+
node._metadata = {}
416413

417414
self._free_nodes.append(node)
418415
return weight

hugr-py/src/hugr/hugr/node_port.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def metadata(self) -> dict[str, object]:
147147
return self.to_node()._metadata
148148

149149

150-
@dataclass(frozen=True, eq=True, order=True)
150+
@dataclass(eq=True, order=True)
151151
class Node(ToNode):
152152
"""Node in hierarchical :class:`Hugr <hugr.hugr.Hugr>` graph,
153153
with globally unique index.
@@ -224,6 +224,9 @@ def to_node(self) -> Node:
224224
def __repr__(self) -> str:
225225
return f"Node({self.idx})"
226226

227+
def __hash__(self) -> int:
228+
return hash(self.idx)
229+
227230

228231
P = TypeVar("P", InPort, OutPort)
229232

0 commit comments

Comments
 (0)