ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Unity Editor实现一个比AssetDatabase.FindAssets内存和CPU都更友好的替代方案

Unity Editor实现一个比AssetDatabase.FindAssets内存和CPU都更友好的替代方案 目录前言优化方案性能测试测试结果AssetDatabase vs AssetDataUtil 性能测试对比GC Allocations (Undefineds)Execution Time (Milliseconds)结论GC方面执行时间方面前言最近在做一个unity编辑器项目需要找到在某个路径下名字包含“ArtPrefab”的预制体。这导致AssetDatabase.FindAssets被频繁调用而它会返回完整的string[]AssetDatabase.FindAssets源码publicstaticstring[]FindAssets(stringfilter,string[]searchInFolders){SearchFiltersearchFilternewSearchFilter{searchAreaSearchFilter.SearchArea.AllAssets};SearchUtility.ParseSearchString(filter,searchFilter);if(searchInFolders!nullsearchInFolders.Length!0){searchFilter.folderssearchInFolders;searchFilter.searchAreaSearchFilter.SearchArea.SelectedFolders;}returnFindAssets(searchFilter);}internalstaticstring[]FindAssets(SearchFiltersearchFilter){return(frompropertyinFindAllAssets(searchFilter)selectproperty.guid).Distinct().ToArray();}源码中还用了Distinct().ToArray()性能进一步下降。但实际上我们的需求并不需要遍历所有找到的预制体遍历时找到需要的预制体就可以返回了。这导致了大量没用的内存分配。需求伪代码varguidsAssetDatabase.FindAssets(SearchFilter,SearchFolders);foreach(varguidinguids){varpathAssetDatabase.GUIDToAssetPath(guid);if(!string.IsNullOrEmpty(path))return;}然而源码中是有返回IEnumerable版本的函数的但不知什么原因是internal的internalstaticIEnumerableHierarchyPropertyFindAllAssets(SearchFiltersearchFilter){IEnumeratorHierarchyPropertyenumeratorEnumerateAllAssets(searchFilter);while(enumerator.MoveNext()){yieldreturnenumerator.Current;}}internalstaticIEnumeratorHierarchyPropertyEnumerateAllAssets(SearchFiltersearchFilter){if(searchFilter.folders!nullsearchFilter.folders.Length!0searchFilter.searchAreaSearchFilter.SearchArea.SelectedFolders){returnFindInFolders(searchFilter,(HierarchyPropertyp)p);}returnFindEverywhere(searchFilter,(HierarchyPropertyp)p);}优化方案既然源码已经有IEnumerable的方案最简单的方法就是直接反射这个函数。于是直接让AI做AI做这种工具向的实在太方便了。省掉了去重和数组转换的损耗直接 yield return EnumerateAllAssets函数的结果。yield return 相比于 返回一个完整的string[]没有一次性的数组分配非常适合我们的需求。usingSystem;usingSystem.Collections.Generic;usingSystem.Reflection;usingUnityEditor;publicstaticclassAssetDataUtil{// -------------------- 缓存的反射成员 --------------------privatestaticreadonlyMethodInfos_EnumerateAllAssets;privatestaticreadonlyMethodInfos_CreateSearchFilterFromString;privatestaticreadonlyTypes_SearchFilterType;privatestaticreadonlyTypes_HierarchyPropertyType;privatestaticreadonlyPropertyInfos_GuidProperty;privatestaticreadonlyPropertyInfos_SearchAreaProperty;privatestaticreadonlyPropertyInfos_FoldersProperty;privatestaticreadonlyobjects_SearchAreaAllAssets;privatestaticreadonlyobjects_SearchAreaSelectedFolders;staticAssetDataUtil(){varunityEditorAssemblytypeof(AssetDatabase).Assembly;// 1. EnumerateAllAssets (internal static)s_EnumerateAllAssetstypeof(AssetDatabase).GetMethod(EnumerateAllAssets,BindingFlags.NonPublic|BindingFlags.Static);if(s_EnumerateAllAssetsnull)thrownewInvalidOperationException(无法反射 AssetDatabase.EnumerateAllAssets。);// 2. SearchFilter 类型s_SearchFilterTypeunityEditorAssembly.GetType(UnityEditor.SearchFilter);if(s_SearchFilterTypenull)thrownewInvalidOperationException(无法反射 UnityEditor.SearchFilter 类型。);// 3. SearchFilter.CreateSearchFilterFromString (internal static)s_CreateSearchFilterFromStrings_SearchFilterType.GetMethod(CreateSearchFilterFromString,BindingFlags.NonPublic|BindingFlags.Static);if(s_CreateSearchFilterFromStringnull)thrownewInvalidOperationException(无法反射 SearchFilter.CreateSearchFilterFromString。);// 4. SearchFilter 的 public 属性s_SearchAreaPropertys_SearchFilterType.GetProperty(searchArea);s_FoldersPropertys_SearchFilterType.GetProperty(folders);if(s_SearchAreaPropertynull||s_FoldersPropertynull)thrownewInvalidOperationException(无法反射 SearchFilter 的 searchArea/folders 属性。);// 5. SearchArea 枚举值缓存varsearchAreaTypes_SearchFilterType.GetNestedType(SearchArea);s_SearchAreaAllAssetsEnum.Parse(searchAreaType,AllAssets);s_SearchAreaSelectedFoldersEnum.Parse(searchAreaType,SelectedFolders);// 6. HierarchyProperty 及 guid 属性s_HierarchyPropertyTypeunityEditorAssembly.GetType(UnityEditor.HierarchyProperty);s_GuidPropertys_HierarchyPropertyType?.GetProperty(guid,BindingFlags.Public|BindingFlags.Instance);}privatestaticreadonlyobject[]FilterDirectorynewobject[]{};privatestaticreadonlyobject[]SearchFilterDirectorynewobject[1];/// summary/// 查找符合过滤条件的资源 GUID延迟枚举无一次性数组分配/// /summarypublicstaticIEnumerablestringFindAssets(stringfilter,string[]searchInFoldersnull){// 通过 SearchFilter.CreateSearchFilterFromString 创建并解析 filterFilterDirectory[0]filter;varsearchFilters_CreateSearchFilterFromString.Invoke(null,FilterDirectory);// 设置默认搜索区域AllAssetss_SearchAreaProperty.SetValue(searchFilter,s_SearchAreaAllAssets);// 如果指定了搜索文件夹则切换区域并设置 Foldersif(searchInFolders!nullsearchInFolders.Length0){s_FoldersProperty.SetValue(searchFilter,searchInFolders);s_SearchAreaProperty.SetValue(searchFilter,s_SearchAreaSelectedFolders);}// 获取底层迭代器SearchFilterDirectory[0]searchFilter;if(s_EnumerateAllAssets.Invoke(null,SearchFilterDirectory)isnotSystem.Collections.IEnumeratorenumerator)yieldbreak;while(enumerator.MoveNext()){varcurrentenumerator.Current;if(current!nulls_GuidProperty!null){varguids_GuidProperty.GetValue(current)asstring;if(!string.IsNullOrEmpty(guid))yieldreturnguid;}}}}性能测试由于用了反射性能表现到底如何还得测试。usingNUnit.Framework;usingUnity.PerformanceTesting;usingUnityEditor;/// summary/// 使用 Unity Performance Testing Framework 对比 AssetDatabase.FindAssets 与 AssetDataUtil.FindAssets/// 运行方式Window → General → Test Runner → EditMode → Run Selected/// /summarypublicclassAssetSearchPerformanceTests{privateconststringTestDirectoryAssets;privateconststringSearchFiltert:prefab ArtPrefab;// 复用数组避免 GCprivatestaticreadonlystring[]SearchFoldersnew[]{TestDirectory};privatestaticreadonlystring[]FindAssetsDirectorynewstring[1];[OneTimeSetUp]publicvoidOneTimeSetUp(){if(!System.IO.Directory.Exists(TestDirectory)){Assert.Ignore($测试目录不存在:{TestDirectory}跳过性能测试。);}}[Test,Performance]publicvoidAssetDatabase_FindAssets(){Measure.Method(RunAssetDatabaseSearch).SampleGroup(newSampleGroup(AssetDatabase.FindAssets,SampleUnit.Millisecond)).WarmupCount(5).MeasurementCount(20).IterationsPerMeasurement(1).GC().Run();}[Test,Performance]publicvoidAssetDataUtil_FindAssets(){Measure.Method(RunAssetDataUtilSearch).SampleGroup(newSampleGroup(AssetDataUtil.FindAssets,SampleUnit.Millisecond)).WarmupCount(5).MeasurementCount(20).IterationsPerMeasurement(1).GC().Run();}[Test,Performance]publicvoidAssetDatabase_FindAssets_WithFullIteration(){Measure.Method((){varguidsAssetDatabase.FindAssets(SearchFilter,SearchFolders);varcount0;foreach(varguidinguids){if(!string.IsNullOrEmpty(guid))count;}}).SampleGroup(newSampleGroup(AssetDatabase.FindAssetsIteration,SampleUnit.Millisecond)).WarmupCount(5).MeasurementCount(20).GC().Run();}[Test,Performance]publicvoidAssetDataUtil_FindAssets_WithFullIteration(){Measure.Method((){FindAssetsDirectory[0]TestDirectory;varguidsAssetDataUtil.FindAssets(SearchFilter,FindAssetsDirectory);varcount0;foreach(varguidinguids){if(!string.IsNullOrEmpty(guid))count;}}).SampleGroup(newSampleGroup(AssetDataUtil.FindAssetsIteration,SampleUnit.Millisecond)).WarmupCount(5).MeasurementCount(20).GC().Run();}[Test,Performance]publicvoidAssetDatabase_FindAssets_WithOneIteration(){Measure.Method((){varguidsAssetDatabase.FindAssets(SearchFilter,SearchFolders);foreach(varguidinguids){if(!string.IsNullOrEmpty(guid))return;}}).SampleGroup(newSampleGroup(AssetDatabase.FindAssetsIteration,SampleUnit.Millisecond)).WarmupCount(5).MeasurementCount(20).GC().Run();}[Test,Performance]publicvoidAssetDataUtil_FindAssets_WithOneIteration(){Measure.Method((){FindAssetsDirectory[0]TestDirectory;varguidsAssetDataUtil.FindAssets(SearchFilter,FindAssetsDirectory);foreach(varguidinguids){if(!string.IsNullOrEmpty(guid))return;}}).SampleGroup(newSampleGroup(AssetDataUtil.FindAssetsIteration,SampleUnit.Millisecond)).WarmupCount(5).MeasurementCount(20).GC().Run();}privatevoidRunAssetDatabaseSearch(){varguidsAssetDatabase.FindAssets(SearchFilter,SearchFolders);// 强制迭代防止延迟枚举导致测量偏差foreach(var_inguids){}}privatevoidRunAssetDataUtilSearch(){FindAssetsDirectory[0]TestDirectory;varguidsAssetDataUtil.FindAssets(SearchFilter,FindAssetsDirectory);foreach(var_inguids){}}}测试结果AssetDatabase vs AssetDataUtil 性能测试对比GC Allocations (Undefineds)Test CaseMinMedianMaxAvgStdDevSampleCountSumAssetDatabase.FindAssets91.0091.00178.0094.4817.05252362.00AssetDatabase.FindAssetsIteration (Full)91.0091.0092.0091.040.20252276.00AssetDatabase.FindAssetsIteration (One)90.0090.0092.0090.480.57252262.00AssetDataUtil.FindAssets88.0088.00173.0091.4016.66252285.00AssetDataUtil.FindAssetsIteration (Full)88.0088.00118.0089.245.87252231.00AssetDataUtil.FindAssetsIteration (One)82.0082.0084.0082.440.57252061.00Execution Time (Milliseconds)Test CaseMinMedianMaxAvgStdDevSampleCountSumAssetDatabase.FindAssets1.131.181.261.190.042023.79AssetDatabase.FindAssetsIteration (Full)1.181.211.331.220.042024.38AssetDatabase.FindAssetsIteration (One)1.151.201.261.200.022024.00AssetDataUtil.FindAssets1.201.251.381.250.042025.06AssetDataUtil.FindAssetsIteration (Full)1.221.251.321.260.032025.21AssetDataUtil.FindAssetsIteration (One)0.190.230.270.230.02204.50结论GC方面优化后的AssetDataUtil.FindAssets在同等情况下表现都更好。执行时间方面如果是全遍历原生的AssetDatabase.FindAssets更好因为直接遍历数组如果是少量遍历测试中读到第一个元素就返回则优化后的AssetDataUtil.FindAssets表现好很多IEnumerable的优势就体现出来了。几乎是O(1)和O(n)的区别
返回列表