如何优化泛型组件缓存方法HadCachedComponent<T>的执行速度?
泛型组件缓存方法的优化方案
针对你遇到的泛型缓存比原生GetComponent慢的问题,这里有几个实用的优化方向,兼顾性能和易用性:
1. 用RuntimeTypeHandle替代Type作为字典键
Type是引用类型,哈希计算和查找的开销比值类型RuntimeTypeHandle高。把字典的键换成RuntimeTypeHandle,能显著降低查找耗时:
using System.Runtime.CompilerServices; using UnityEngine; readonly Dictionary<RuntimeTypeHandle, object> m_cachedComponents = new Dictionary<RuntimeTypeHandle, object>(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HadCachedComponent<T>(out T component) where T : Component { var typeHandle = typeof(T).TypeHandle; if (m_cachedComponents.TryGetValue(typeHandle, out var obj)) { component = Unsafe.As<object, T>(ref obj); // 跳过CLR类型检查的强制转换 return obj != null; } var found = TryGetComponent<T>(out component); m_cachedComponents.TryAdd(typeHandle, found ? component : null); return found; }
这里用Unsafe.As替代普通的(T)obj,省去类型安全检查的开销,需要引用System.Runtime.CompilerServices.Unsafe命名空间。
2. 拆分缓存容器:分离存在/不存在的组件
原来的方案把null也存入字典,会增加条目数拖慢查找。可以用两个容器分别记录:一个字典存已找到的组件,一个哈希集合存确认不存在的组件:
using System.Runtime.CompilerServices; using UnityEngine; readonly Dictionary<RuntimeTypeHandle, object> m_existingComponents = new Dictionary<RuntimeTypeHandle, object>(); readonly HashSet<RuntimeTypeHandle> m_missingComponents = new HashSet<RuntimeTypeHandle>(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HadCachedComponent<T>(out T component) where T : Component { var typeHandle = typeof(T).TypeHandle; // 优先查已缓存的存在组件 if (m_existingComponents.TryGetValue(typeHandle, out var obj)) { component = Unsafe.As<object, T>(ref obj); return true; } // 再查已确认不存在的组件 if (m_missingComponents.Contains(typeHandle)) { component = null; return false; } // 首次查找组件 var found = TryGetComponent<T>(out component); if (found) m_existingComponents.TryAdd(typeHandle, component); else m_missingComponents.Add(typeHandle); return found; }
这种方式字典条目更少,查找效率更高,也避免了存储null的额外开销。
3. 预缓存高频组件
如果某些组件是高频调用的,可以在Awake或Start里提前缓存,让后续调用直接命中缓存,性能接近硬编码方案:
private void Awake() { // 提前缓存常用组件,后续调用直接取缓存 HadCachedComponent<Stealth>(out _); HadCachedComponent<Collider>(out _); HadCachedComponent<Rigidbody>(out _); }
4. 强制方法内联减少调用开销
给泛型方法加上[MethodImpl(MethodImplOptions.AggressiveInlining)]标记,让编译器把方法体直接嵌入调用处,减少方法调用的栈开销,对高频调用的方法提升明显。
5. 初始化字典时指定容量
如果能预估需要缓存的组件数量,初始化字典时提前设置容量,避免后续扩容带来的性能损耗:
// 假设最多缓存20种组件,提前设置容量避免扩容 readonly Dictionary<RuntimeTypeHandle, object> m_cachedComponents = new Dictionary<RuntimeTypeHandle, object>(20);
折中方案:自动生成硬编码缓存代码
如果追求极致性能又不想手动写每个组件的缓存方法,可以用Unity代码生成工具(比如Roslyn源码生成器、[InitializeOnLoad]脚本),扫描项目中用到的Component子类,自动在Target类中生成对应的硬编码缓存方法,兼顾性能和易用性。
内容的提问来源于stack exchange,提问作者NotSoEpicCodeMaster
相关产品推荐
相关产品推荐

