目录
前言
最近在做一个unity编辑器项目,需要找到在某个路径下,名字包含“ArtPrefab”的预制体。
这导致AssetDatabase.FindAssets被频繁调用,而它会返回完整的string[]
AssetDatabase.FindAssets源码:
public static string[] FindAssets(string filter, string[] searchInFolders)
{
SearchFilter searchFilter = new SearchFilter
{
searchArea = SearchFilter.SearchArea.AllAssets
};
SearchUtility.ParseSearchString(filter, searchFilter);
if (searchInFolders != null && searchInFolders.Length != 0)
{
searchFilter.folders = searchInFolders;
searchFilter.searchArea = SearchFilter.SearchArea.SelectedFolders;
}
return FindAssets(searchFilter);
}
internal static string[] FindAssets(SearchFilter searchFilter)
{
return (from property in FindAllAssets(searchFilter)
select property.guid).Distinct().ToArray();
}
源码中还用了Distinct().ToArray(),性能进一步下降。
但实际上我们的需求并不需要遍历所有找到的预制体,遍历时,找到需要的预制体就可以返回了。这导致了大量没用的内存分配。
需求伪代码:
var guids = AssetDatabase.FindAssets(SearchFilter, SearchFolders);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(path))
return;
}
然而源码中是有返回IEnumerable版本的函数的,但不知什么原因是internal的
internal static IEnumerable<HierarchyProperty> FindAllAssets(SearchFilter searchFilter)
{
IEnumerator<HierarchyProperty> enumerator = EnumerateAllAssets(searchFilter);
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
internal static IEnumerator<HierarchyProperty> EnumerateAllAssets(SearchFilter searchFilter)
{
if (searchFilter.folders != null && searchFilter.folders.Length != 0 && searchFilter.searchArea == SearchFilter.SearchArea.SelectedFolders)
{
return FindInFolders(searchFilter, (HierarchyProperty p) => p);
}
return FindEverywhere(searchFilter, (HierarchyProperty p) => p);
}
优化方案
既然源码已经有IEnumerable的方案,最简单的方法就是直接反射这个函数。于是直接让AI做,AI做这种工具向的实在太方便了。
省掉了去重和数组转换的损耗,直接 yield return EnumerateAllAssets函数的结果。
yield return 相比于 返回一个完整的string[],没有一次性的数组分配,非常适合我们的需求。
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
public static class AssetDataUtil
{
// -------------------- 缓存的反射成员 --------------------
private static readonly MethodInfo s_EnumerateAllAssets;
private static readonly MethodInfo s_CreateSearchFilterFromString;
private static readonly Type s_SearchFilterType;
private static readonly Type s_HierarchyPropertyType;
private static readonly PropertyInfo s_GuidProperty;
private static readonly PropertyInfo s_SearchAreaProperty;
private static readonly PropertyInfo s_FoldersProperty;
private static readonly object s_SearchAreaAllAssets;
private static readonly object s_SearchAreaSelectedFolders;
static AssetDataUtil()
{
var unityEditorAssembly = typeof(AssetDatabase).Assembly;
// 1. EnumerateAllAssets (internal static)
s_EnumerateAllAssets = typeof(AssetDatabase).GetMethod(
"EnumerateAllAssets",
BindingFlags.NonPublic | BindingFlags.Static);
if (s_EnumerateAllAssets == null)
throw new InvalidOperationException("无法反射 AssetDatabase.EnumerateAllAssets。");
// 2. SearchFilter 类型
s_SearchFilterType = unityEditorAssembly.GetType("UnityEditor.SearchFilter");
if (s_SearchFilterType == null)
throw new InvalidOperationException("无法反射 UnityEditor.SearchFilter 类型。");
// 3. SearchFilter.CreateSearchFilterFromString (internal static)
s_CreateSearchFilterFromString = s_SearchFilterType.GetMethod(
"CreateSearchFilterFromString",
BindingFlags.NonPublic | BindingFlags.Static);
if (s_CreateSearchFilterFromString == null)
throw new InvalidOperationException("无法反射 SearchFilter.CreateSearchFilterFromString。");
// 4. SearchFilter 的 public 属性
s_SearchAreaProperty = s_SearchFilterType.GetProperty("searchArea");
s_FoldersProperty = s_SearchFilterType.GetProperty("folders");
if (s_SearchAreaProperty == null || s_FoldersProperty == null)
throw new InvalidOperationException("无法反射 SearchFilter 的 searchArea/folders 属性。");
// 5. SearchArea 枚举值缓存
var searchAreaType = s_SearchFilterType.GetNestedType("SearchArea");
s_SearchAreaAllAssets = Enum.Parse(searchAreaType, "AllAssets");
s_SearchAreaSelectedFolders = Enum.Parse(searchAreaType, "SelectedFolders");
// 6. HierarchyProperty 及 guid 属性
s_HierarchyPropertyType = unityEditorAssembly.GetType("UnityEditor.HierarchyProperty");
s_GuidProperty = s_HierarchyPropertyType?.GetProperty("guid", BindingFlags.Public | BindingFlags.Instance);
}
private static readonly object[] FilterDirectory = new object[] { "" };
private static readonly object[] SearchFilterDirectory = new object[1];
/// <summary>
/// 查找符合过滤条件的资源 GUID(延迟枚举,无一次性数组分配)
/// </summary>
public static IEnumerable<string> FindAssets(string filter, string[] searchInFolders = null)
{
// 通过 SearchFilter.CreateSearchFilterFromString 创建并解析 filter
FilterDirectory[0] = filter;
var searchFilter = s_CreateSearchFilterFromString.Invoke(null, FilterDirectory);
// 设置默认搜索区域(AllAssets)
s_SearchAreaProperty.SetValue(searchFilter, s_SearchAreaAllAssets);
// 如果指定了搜索文件夹,则切换区域并设置 Folders
if (searchInFolders != null && searchInFolders.Length > 0)
{
s_FoldersProperty.SetValue(searchFilter, searchInFolders);
s_SearchAreaProperty.SetValue(searchFilter, s_SearchAreaSelectedFolders);
}
// 获取底层迭代器
SearchFilterDirectory[0] = searchFilter;
if (s_EnumerateAllAssets.Invoke(null, SearchFilterDirectory) is not System.Collections.IEnumerator enumerator)
yield break;
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (current != null && s_GuidProperty != null)
{
var guid = s_GuidProperty.GetValue(current) as string;
if (!string.IsNullOrEmpty(guid))
yield return guid;
}
}
}
}
性能测试
由于用了反射,性能表现到底如何还得测试。
using NUnit.Framework;
using Unity.PerformanceTesting;
using UnityEditor;
/// <summary>
/// 使用 Unity Performance Testing Framework 对比 AssetDatabase.FindAssets 与 AssetDataUtil.FindAssets
/// 运行方式:Window → General → Test Runner → EditMode → Run Selected
/// </summary>
public class AssetSearchPerformanceTests
{
private const string TestDirectory = "Assets";
private const string SearchFilter = "t:prefab ArtPrefab";
// 复用数组避免 GC
private static readonly string[] SearchFolders = new[] { TestDirectory };
private static readonly string[] FindAssetsDirectory = new string[1];
[OneTimeSetUp]
public void OneTimeSetUp()
{
if (!System.IO.Directory.Exists(TestDirectory))
{
Assert.Ignore($"测试目录不存在: {TestDirectory},跳过性能测试。");
}
}
[Test, Performance]
public void AssetDatabase_FindAssets()
{
Measure.Method(RunAssetDatabaseSearch)
.SampleGroup(new SampleGroup("AssetDatabase.FindAssets", SampleUnit.Millisecond))
.WarmupCount(5)
.MeasurementCount(20)
.IterationsPerMeasurement(1)
.GC()
.Run();
}
[Test, Performance]
public void AssetDataUtil_FindAssets()
{
Measure.Method(RunAssetDataUtilSearch)
.SampleGroup(new SampleGroup("AssetDataUtil.FindAssets", SampleUnit.Millisecond))
.WarmupCount(5)
.MeasurementCount(20)
.IterationsPerMeasurement(1)
.GC()
.Run();
}
[Test, Performance]
public void AssetDatabase_FindAssets_WithFullIteration()
{
Measure.Method(() =>
{
var guids = AssetDatabase.FindAssets(SearchFilter, SearchFolders);
var count = 0;
foreach (var guid in guids)
{
if (!string.IsNullOrEmpty(guid))
count++;
}
})
.SampleGroup(new SampleGroup("AssetDatabase.FindAssets+Iteration", SampleUnit.Millisecond))
.WarmupCount(5)
.MeasurementCount(20)
.GC()
.Run();
}
[Test, Performance]
public void AssetDataUtil_FindAssets_WithFullIteration()
{
Measure.Method(() =>
{
FindAssetsDirectory[0] = TestDirectory;
var guids = AssetDataUtil.FindAssets(SearchFilter, FindAssetsDirectory);
var count = 0;
foreach (var guid in guids)
{
if (!string.IsNullOrEmpty(guid))
count++;
}
})
.SampleGroup(new SampleGroup("AssetDataUtil.FindAssets+Iteration", SampleUnit.Millisecond))
.WarmupCount(5)
.MeasurementCount(20)
.GC()
.Run();
}
[Test, Performance]
public void AssetDatabase_FindAssets_WithOneIteration()
{
Measure.Method(() =>
{
var guids = AssetDatabase.FindAssets(SearchFilter, SearchFolders);
foreach (var guid in guids)
{
if (!string.IsNullOrEmpty(guid))
return;
}
})
.SampleGroup(new SampleGroup("AssetDatabase.FindAssets+Iteration", SampleUnit.Millisecond))
.WarmupCount(5)
.MeasurementCount(20)
.GC()
.Run();
}
[Test, Performance]
public void AssetDataUtil_FindAssets_WithOneIteration()
{
Measure.Method(() =>
{
FindAssetsDirectory[0] = TestDirectory;
var guids = AssetDataUtil.FindAssets(SearchFilter, FindAssetsDirectory);
foreach (var guid in guids)
{
if (!string.IsNullOrEmpty(guid))
return;
}
})
.SampleGroup(new SampleGroup("AssetDataUtil.FindAssets+Iteration", SampleUnit.Millisecond))
.WarmupCount(5)
.MeasurementCount(20)
.GC()
.Run();
}
private void RunAssetDatabaseSearch()
{
var guids = AssetDatabase.FindAssets(SearchFilter, SearchFolders);
// 强制迭代,防止延迟枚举导致测量偏差
foreach (var _ in guids) { }
}
private void RunAssetDataUtilSearch()
{
FindAssetsDirectory[0] = TestDirectory;
var guids = AssetDataUtil.FindAssets(SearchFilter, FindAssetsDirectory);
foreach (var _ in guids) { }
}
}
测试结果
AssetDatabase vs AssetDataUtil 性能测试对比
GC Allocations (Undefineds)
| Test Case | Min | Median | Max | Avg | StdDev | SampleCount | Sum |
|---|---|---|---|---|---|---|---|
| AssetDatabase.FindAssets | 91.00 | 91.00 | 178.00 | 94.48 | 17.05 | 25 | 2362.00 |
| AssetDatabase.FindAssets+Iteration (Full) | 91.00 | 91.00 | 92.00 | 91.04 | 0.20 | 25 | 2276.00 |
| AssetDatabase.FindAssets+Iteration (One) | 90.00 | 90.00 | 92.00 | 90.48 | 0.57 | 25 | 2262.00 |
| AssetDataUtil.FindAssets | 88.00 | 88.00 | 173.00 | 91.40 | 16.66 | 25 | 2285.00 |
| AssetDataUtil.FindAssets+Iteration (Full) | 88.00 | 88.00 | 118.00 | 89.24 | 5.87 | 25 | 2231.00 |
| AssetDataUtil.FindAssets+Iteration (One) | 82.00 | 82.00 | 84.00 | 82.44 | 0.57 | 25 | 2061.00 |
Execution Time (Milliseconds)
| Test Case | Min | Median | Max | Avg | StdDev | SampleCount | Sum |
|---|---|---|---|---|---|---|---|
| AssetDatabase.FindAssets | 1.13 | 1.18 | 1.26 | 1.19 | 0.04 | 20 | 23.79 |
| AssetDatabase.FindAssets+Iteration (Full) | 1.18 | 1.21 | 1.33 | 1.22 | 0.04 | 20 | 24.38 |
| AssetDatabase.FindAssets+Iteration (One) | 1.15 | 1.20 | 1.26 | 1.20 | 0.02 | 20 | 24.00 |
| AssetDataUtil.FindAssets | 1.20 | 1.25 | 1.38 | 1.25 | 0.04 | 20 | 25.06 |
| AssetDataUtil.FindAssets+Iteration (Full) | 1.22 | 1.25 | 1.32 | 1.26 | 0.03 | 20 | 25.21 |
| AssetDataUtil.FindAssets+Iteration (One) | 0.19 | 0.23 | 0.27 | 0.23 | 0.02 | 20 | 4.50 |
结论
GC方面
优化后的AssetDataUtil.FindAssets在同等情况下,表现都更好。
执行时间方面
如果是全遍历,原生的AssetDatabase.FindAssets更好,因为直接遍历数组;
如果是少量遍历(测试中读到第一个元素就返回),则优化后的AssetDataUtil.FindAssets表现好很多,IEnumerable的优势就体现出来了。几乎是O(1)和O(n)的区别
496



被折叠的 条评论
为什么被折叠?



