Skip to content

Commit 8548e29

Browse files
experiment: bypass exception path for hot comparator types
1 parent e2e4ddf commit 8548e29

1 file changed

Lines changed: 55 additions & 52 deletions

File tree

codeflash/verification/comparator.py

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,58 @@ def _get_wrapped_exception(exc: BaseException) -> Optional[BaseException]: # no
180180
def comparator(orig: Any, new: Any, superset_obj: bool = False) -> bool:
181181
"""Compare two objects for equality recursively. If superset_obj is True, the new object is allowed to have more keys than the original object. However, the existing keys/values must be equivalent."""
182182
try:
183-
# Handle exceptions specially - before type check to allow wrapper comparison
183+
orig_type = type(orig)
184+
new_type = type(new)
185+
186+
# Fast-path: type identity checks for the most common return-value types.
187+
# `orig_type is T` is a single pointer comparison — cheaper than frozenset hash
188+
# lookup or isinstance MRO traversal — and these 4 types dominate real workloads.
189+
if orig_type is new_type:
190+
if orig_type is str:
191+
if orig == new:
192+
return True
193+
if _is_temp_path(orig) and _is_temp_path(new):
194+
return _normalize_temp_path(orig) == _normalize_temp_path(new)
195+
return False
196+
if orig_type is list or orig_type is tuple:
197+
if len(orig) != len(new):
198+
return False
199+
if orig:
200+
all_fast_scalars = True
201+
for idx, elem1 in enumerate(orig):
202+
elem_type = type(elem1)
203+
if elem_type is not type(new[idx]) or elem_type not in _FAST_SEQUENCE_EQ_TYPES:
204+
all_fast_scalars = False
205+
break
206+
if all_fast_scalars and orig == new:
207+
return True
208+
209+
for idx, elem1 in enumerate(orig):
210+
if not comparator(elem1, new[idx], superset_obj):
211+
return False
212+
return True
213+
if orig_type is dict:
214+
if superset_obj:
215+
return all(k in new and comparator(v, new[k], superset_obj) for k, v in orig.items())
216+
if len(orig) != len(new):
217+
return False
218+
for key in orig:
219+
if key not in new:
220+
return False
221+
if not comparator(orig[key], new[key], superset_obj):
222+
return False
223+
return True
224+
if orig_type is float:
225+
if orig == new:
226+
return True
227+
if math.isnan(orig) and math.isnan(new):
228+
return True
229+
return math.isclose(orig, new)
230+
# O(1) frozenset lookup for remaining common types (int, bool, None, Decimal, etc.)
231+
if orig_type in _IDENTITY_EQ_TYPES:
232+
return orig == new
233+
234+
# Handle exceptions specially - before the generic type-mismatch path to allow wrapper comparison
184235
if isinstance(orig, BaseException) and isinstance(new, BaseException):
185236
if isinstance(orig, PicklePlaceholderAccessError) or isinstance(new, PicklePlaceholderAccessError):
186237
# If this error was raised, there was an attempt to access the PicklePlaceholder, which represents an unpickleable object.
@@ -189,7 +240,7 @@ def comparator(orig: Any, new: Any, superset_obj: bool = False) -> bool:
189240
return False
190241

191242
# If types match exactly, compare attributes
192-
if type(orig) is type(new):
243+
if orig_type is new_type:
193244
orig_dict = {k: v for k, v in orig.__dict__.items() if not k.startswith("_")}
194245
new_dict = {k: v for k, v in new.__dict__.items() if not k.startswith("_")}
195246
return comparator(orig_dict, new_dict, superset_obj)
@@ -207,59 +258,11 @@ def comparator(orig: Any, new: Any, superset_obj: bool = False) -> bool:
207258

208259
return False
209260

210-
orig_type = type(orig)
211-
if orig_type is not type(new):
261+
if orig_type is not new_type:
212262
# distinct type objects are created at runtime, even if the class code is exactly the same, so we can only compare the names
213-
if orig_type.__name__ != type(new).__name__ or orig_type.__qualname__ != type(new).__qualname__:
263+
if orig_type.__name__ != new_type.__name__ or orig_type.__qualname__ != new_type.__qualname__:
214264
return False
215265

216-
# Fast-path: type identity checks for the most common return-value types.
217-
# `orig_type is T` is a single pointer comparison — cheaper than frozenset hash
218-
# lookup or isinstance MRO traversal — and these 4 types dominate real workloads.
219-
if orig_type is str:
220-
if orig == new:
221-
return True
222-
if _is_temp_path(orig) and _is_temp_path(new):
223-
return _normalize_temp_path(orig) == _normalize_temp_path(new)
224-
return False
225-
if orig_type is list or orig_type is tuple:
226-
if len(orig) != len(new):
227-
return False
228-
if orig:
229-
all_fast_scalars = True
230-
for idx, elem1 in enumerate(orig):
231-
elem_type = type(elem1)
232-
if elem_type is not type(new[idx]) or elem_type not in _FAST_SEQUENCE_EQ_TYPES:
233-
all_fast_scalars = False
234-
break
235-
if all_fast_scalars and orig == new:
236-
return True
237-
238-
for idx, elem1 in enumerate(orig):
239-
if not comparator(elem1, new[idx], superset_obj):
240-
return False
241-
return True
242-
if orig_type is dict:
243-
if superset_obj:
244-
return all(k in new and comparator(v, new[k], superset_obj) for k, v in orig.items())
245-
if len(orig) != len(new):
246-
return False
247-
for key in orig:
248-
if key not in new:
249-
return False
250-
if not comparator(orig[key], new[key], superset_obj):
251-
return False
252-
return True
253-
if orig_type is float:
254-
if orig == new:
255-
return True
256-
if math.isnan(orig) and math.isnan(new):
257-
return True
258-
return math.isclose(orig, new)
259-
# O(1) frozenset lookup for remaining common types (int, bool, None, Decimal, etc.)
260-
if orig_type in _IDENTITY_EQ_TYPES:
261-
return orig == new
262-
263266
# Slower isinstance path for subclasses (deque, ChainMap, etc.)
264267
if isinstance(orig, (list, tuple, deque, ChainMap)):
265268
if len(orig) != len(new):

0 commit comments

Comments
 (0)