From 6bc4f20269c51c8055e495c24289ab56e2f69599 Mon Sep 17 00:00:00 2001 From: Dixing Xu Date: Wed, 28 Oct 2026 00:02:04 +0000 Subject: [PATCH] perf(TypeTreeNode): parse_blob -- 2.6x faster warm test_save Cache clean names within each blob and recognize unchanged ASCII identifiers before constructing nodes. Reuse the empty-name constructor path for cached names and pass ordinary node fields positionally, while preserving keyword construction and name cleaning for subclasses. Warmed tests/test_main.py::test_save: 91.7 ms -> 35.0 ms per call (median of five standard-library timeit repeats of 30 calls). Three frozen-harness ratios: 0.414, 0.397, 0.425. Native tests: 44 passed; full Ruff lint and formatting passed. Co-Authored-By: Aiden --- UnityPy/helpers/TypeTreeNode.py | 45 ++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/UnityPy/helpers/TypeTreeNode.py b/UnityPy/helpers/TypeTreeNode.py index 3f4c82a4..e404ead9 100644 --- a/UnityPy/helpers/TypeTreeNode.py +++ b/UnityPy/helpers/TypeTreeNode.py @@ -133,13 +133,46 @@ def read_string(reader: EndianBinaryReader, value: int) -> str: parent = fake_root prev = fake_root + clean_names: dict[str, str] = {} for raw_node in node_struct.iter_unpack(struct_data): - node = cls( - **dict(zip(keys[:3], raw_node[:3])), - **dict(zip(keys[5:], raw_node[5:])), - m_Type=read_string(stringbuffer_reader, raw_node[3]), - m_Name=read_string(stringbuffer_reader, raw_node[4]), - ) + node_type = read_string(stringbuffer_reader, raw_node[3]) + name = read_string(stringbuffer_reader, raw_node[4]) + cache_clean_name = cls is TypeTreeNode and type(name) is str + cached_clean_name = clean_names.get(name) if cache_clean_name else None + if ( + cache_clean_name + and cached_clean_name is None + and name.isascii() + and name.isidentifier() + and name not in ("pass", "from") + ): + cached_clean_name = name + clean_names[name] = name + if cls is TypeTreeNode: + node = cls( + raw_node[1], + node_type, + name if cached_clean_name is None else "", + raw_node[5], + raw_node[0], + [], + raw_node[2], + None, + raw_node[6], + raw_node[7], + raw_node[8] if len(raw_node) > 8 else None, + ) + else: + node = cls( + **{key: raw_node[index] for index, key in enumerate(keys) if index not in (3, 4)}, + m_Type=node_type, + m_Name=name, + ) + if cached_clean_name is not None: + node.m_Name = name + node._clean_name = cached_clean_name + elif cache_clean_name: + clean_names[name] = node._clean_name if node.m_Level > prev.m_Level: stack.append(parent)