FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

perf: precompute address hash instead of per-instance lru_cache · python-zeroconf/python-zeroconf@1a232c4 · GitHub

Commit 1a232c4

Browse files
committed
perf: precompute address hash instead of per-instance lru_cache
ZeroconfIPv4Address/ZeroconfIPv6Address assigned self.__hash__ to a functools.cache(lambda ...) in __init__. Because __hash__ sat in __slots__ the memoization did work, but every address object paid to build a full lru_cache wrapper plus a self-capturing lambda at construction time (and a self -> slot -> wrapper -> lambda -> self reference cycle for the GC to reap). Compute the hash once into a plain int slot and expose it through a real __hash__ method. Construction drops ~25-45% in pure-Python micro- benchmarks; hash() stays memoized via the precomputed slot.
1 parent 433dd40 commit 1a232c4

2 files changed

Lines changed: 27 additions & 5 deletions

File tree

‎src/zeroconf/_utils/ipaddress.py‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from __future__ import annotations
2424

25-
from functools import cache, lru_cache
25+
from functools import lru_cache
2626
from ipaddress import AddressValueError, IPv4Address, IPv6Address, NetmaskValueError
2727
from typing import Any
2828

@@ -34,7 +34,7 @@
3434

3535

3636
class ZeroconfIPv4Address(IPv4Address):
37-
__slots__ = ("__hash__", "_is_link_local", "_is_loopback", "_is_unspecified", "_str", "zc_integer")
37+
__slots__ = ("_hash", "_is_link_local", "_is_loopback", "_is_unspecified", "_str", "zc_integer")
3838

3939
def __init__(self, *args: Any, **kwargs: Any) -> None:
4040
"""Initialize a new IPv4 address."""
@@ -43,13 +43,17 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
4343
self._is_link_local = super().is_link_local
4444
self._is_unspecified = super().is_unspecified
4545
self._is_loopback = super().is_loopback
46-
self.__hash__ = cache(lambda: IPv4Address.__hash__(self)) # type: ignore[method-assign]
46+
self._hash = IPv4Address.__hash__(self)
4747
self.zc_integer = int(self)
4848

4949
def __str__(self) -> str:
5050
"""Return the string representation of the IPv4 address."""
5151
return self._str
5252

53+
def __hash__(self) -> int:
54+
"""Return the precomputed hash of the IPv4 address."""
55+
return self._hash
56+
5357
@property
5458
def is_link_local(self) -> bool:
5559
"""Return True if this is a link-local address."""
@@ -67,7 +71,7 @@ def is_loopback(self) -> bool:
6771

6872

6973
class ZeroconfIPv6Address(IPv6Address):
70-
__slots__ = ("__hash__", "_is_link_local", "_is_loopback", "_is_unspecified", "_str", "zc_integer")
74+
__slots__ = ("_hash", "_is_link_local", "_is_loopback", "_is_unspecified", "_str", "zc_integer")
7175

7276
def __init__(self, *args: Any, **kwargs: Any) -> None:
7377
"""Initialize a new IPv6 address."""
@@ -76,13 +80,17 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
7680
self._is_link_local = super().is_link_local
7781
self._is_unspecified = super().is_unspecified
7882
self._is_loopback = super().is_loopback
79-
self.__hash__ = cache(lambda: IPv6Address.__hash__(self)) # type: ignore[method-assign]
83+
self._hash = IPv6Address.__hash__(self)
8084
self.zc_integer = int(self)
8185

8286
def __str__(self) -> str:
8387
"""Return the string representation of the IPv6 address."""
8488
return self._str
8589

90+
def __hash__(self) -> int:
91+
"""Return the precomputed hash of the IPv6 address."""
92+
return self._hash
93+
8694
@property
8795
def is_link_local(self) -> bool:
8896
"""Return True if this is a link-local address."""

‎tests/utils/test_ipaddress.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ def test_cached_ip_addresses_wrapper():
4848
assert ipv6.is_unspecified is True
4949

5050

51+
def test_address_hash_matches_stdlib_and_dedups():
52+
"""Cached address objects hash like their stdlib equals and dedup in sets."""
53+
v4 = ipaddress.cached_ip_addresses("192.168.1.1")
54+
assert v4 is not None
55+
assert hash(v4) == hash(ipaddress.IPv4Address("192.168.1.1"))
56+
assert hash(v4) == hash(v4)
57+
assert len({v4, ipaddress.ZeroconfIPv4Address("192.168.1.1")}) == 1
58+
59+
v6 = ipaddress.cached_ip_addresses("fe80::1")
60+
assert v6 is not None
61+
assert hash(v6) == hash(ipaddress.IPv6Address("fe80::1"))
62+
assert len({v6, ipaddress.ZeroconfIPv6Address("fe80::1")}) == 1
63+
64+
5165
def test_get_ip_address_object_from_record():
5266
"""Test the get_ip_address_object_from_record."""
5367
# not link local

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL