更新

  • 2019/06/03 输出中增加文件 GUID
  • 2018/10/28 初次发布

介绍

在项目开发中,经常需要查找某个资源被多少其他资源引用,但是 Unity 默认并不支持这种查找,因此需要自行处理。

Unity 查找资源引用可以简单地分为两种方式:标准方式与文本方式。

标准方式

Unity 只提供了正向获取依赖资源的接口,并没有提供反向获取被依赖资源的接口。

可以通过正向获得依赖关系保存后再反向查找,这种方案有以下优缺点:

优点:

  • 完全准确,因为使用的是 Unity 内部引用数据查找,可以保证正确性
  • 查找的范围不局限于 Assets 目录,可以查找到 ProjectSettingsLibrary 中的引用

缺点:

  • 效率极低,由于 Unity 的 API 只能在主线程调用,那么所有可以并行的操作只能退化成单线程操作,结果是 10GiB 大小以上的项目光是建立依赖关系就需要花费大约 5 分钟
  • 不能追踪资源修改,如果资源修改后依赖关系发生变化,那么资源依赖关系需要重新生成

之前在 Unity 资源引用 - 狂飙 介绍了这种方法,并且还编写了这种方法的查找工具:networm/FindReferencesInProject: Find asset references in Unity project.

文本方式

Unity 资源的序列化方式不仅可以是二进制,也可以是文本。

由于 Unity 会在导入资源时为资源生成全局唯一的 GUID,并在所有引用的地方使用,因此通过查找 GUID 即可反向查找资源引用。

优点:

  • 效率高,可以并行执行。

缺点:

  • 搜索范围小,默认只搜索 Assets,而 ProjectSettingsLibrary 等目录并不会搜索到。
  • 即使设置 Asset Serilization ModeForce Text,Unity 并不会将 Library 目录下的相关文件以文本序列化。
  • 由于 Unity 的引用并不全都是以 GUID 形式存在,如 FBX 引用对应的材质(注意:不同 Unity 版本保存形式不同,例如 2017.4 会将材质引用存放在 fbx.meta 中的 ModelImporter/externalObjects 字段),因此基于文本的搜索找到的内容并不是全部。

建议是在项目中减少这种引用的依赖,将引用都改为文本。

需求

由于速度是至关重要的因素:一个工具速度越慢,被使用的频率越低;在慢到一定程度时就不会有人愿意使用。

使用标准方式的方法受到 Unity 的限制,无法进行优化。使用文本方式虽然存在准确性低的问题,但是基本满足要求,并且可以优化的很快。因此以下介绍如何优化文本方式的查找。

环境

  • Unity 2017.4.2f2
  • ripgrep 0.10.0
  • macOS 10.13.6
  • Windows 10

macOS

由于操作系统自建对文件的索引,可以非常快速地使用 mdfind 命令查找指定目录下的内容,在实际项目中只需要 2 秒以内就可以查找完毕。

Unity editor extension that uses spotlight on OSX for lightning fast project reference searches. Asset serialization mode should be set to “Force Text” in the editor settings.

Windows

由于 Windows 并不像 macOS 自建对文件的索引,因此需要使用文本查找工具。

优化

减少查找的内容

经过测试显示瓶颈在 IO 而不是 CPU 上,查找时磁盘使用率会直接飙升到 100%。

减少查找文件的类型

要查找的文件只有 Unity 生成的文本文件,因此二进制文件或其他程序生成的文本文件都可以过滤掉。

主要包括:二进制文件、代码脚本、非 Unity 生成的文件、不包含 GUID 的 meta 文件等等。

使用以下命令显示项目中所有文件的扩展名及其数量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
find Assets -type f | grep -o -E "\.[^\.]+\$" | sort | uniq -c | sort -rn

19163 .meta
3482 .cs
2475 .png
2260 .mat
2101 .prefab
1825 .psd
 866 .fbx
 854 .anim
 435 .controller
 432 .asset
 331 .shader
 210 .json
...

注意:使用正则表达式匹配时,需要使用 "\.[^\.]+\$" 匹配完整的扩展名(因为有部分扩展名是多段的,如:.fbx.meta

因为 Unity 中使用 {fileID: 00000000, guid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, type: 3} 这种类型的 YAML 字典表示,因此搜索时可以使用 fileID: 搜索其引用。

使用以下命令显示项目中所有包含引用的文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
rg --case-sensitive --follow --files-with-matches --no-text --fixed-strings "fileID: " Assets | grep -o -E "\.[^/]*\$" | sort | uniq -c | sort -rn

2258 .mat
2097 .prefab
 853 .anim
 497 .fbx.meta
 435 .controller
 426 .asset
 137 .unity
...

将两个结果比较去重后加入到忽略列表中。

减少查找文件的数量

减少体积小数量多的文件,因为读取次数多,且非常不适合机械硬盘,会让磁头一直寻道。

虽然 .meta 中会存储引用信息,但并不是所有的都保存,使用以下命令显示包含引用的 .meta 文件:

1
2
3
4
5
6
7
8
rg --case-sensitive --follow --files-with-matches --no-text --fixed-strings --iglob '*.meta' "fileID: " Assets | grep -o -E "\.[^/]*\$" | sort | uniq -c | sort -rn

 497 .fbx.meta
  91 .FBX.meta
  22 .cs.meta
   7 .shader.meta
   3 .otf.meta
   2 .ttf.meta

将以上规则从忽略列表中删除。

提高查找的速度

使用快速查找的工具

ripgrep 是一款使用 Rust 语言编写的快速搜索工具,Visual Studio Code 中的搜索功能使用了 ripgrepVisual Studio Code March 2017

ripgrep 有查找速度比较表格:BurntSushi/ripgrep: ripgrep recursively searches directories for a regex pattern

使用固态硬盘

固态硬盘可以大大加快读取小文件的速度,使用之后可以使性能提高一个数量级。

使用操作系统缓存

操作系统支持文件缓存,读取文件后会将文件缓存在内存中,可以加速后续的读取速度。

针对操作系统文件缓存的建议:

  1. 增大内存容量,开发机建议标配 32G 及以上内存
  2. 减少文件数量与总大小,降低缓存未命中机率

这样在重复多次查找引用时能明显感受到速度上有提升。

执行方案

将前续操作获得的结果放到单独的忽略列表文件中,修改完规则后可以使用以下命令查看结果

1
rg --files --case-sensitive --follow --files-with-matches --no-text --fixed-strings --ignore-file Tools/FindProjectReferences/ignore -- Assets | grep -o -E "\.[^\.]+\$" | sort | uniq -c | sort -rn

优化前

1
2
3
4
5
du -sh Assets
 14G    Assets

rg --files --case-sensitive --follow --files-with-matches --no-text --fixed-strings -- Assets | wc -l
   36020

优化后

1
2
3
4
5
6
7
rg --files --case-sensitive --follow --files-with-matches --no-text --fixed-strings --null --ignore-file Tools/FindProjectReferences/ignore -- Assets | xargs -0 du -a | awk '{sum+=$1} END {print sum}'
1347320

1347320*512/1024/1024 = 657MiB

rg --files --case-sensitive --follow --files-with-matches --no-text --fixed-strings --ignore-file ignore -- Assets | wc -l
    7616

参考:Find the total size of certain files within a directory branch - Unix & Linux Stack Exchange

最终结果

比较数字

  • 文件大小 ~13G > ~660M
  • 文件数量 ~36000 > ~7600

实际测试

  • 在 macOS 上使用固态硬盘,查找时间从 40 秒降低到 4 秒。
  • 在 Windows 上从没有固态硬盘时的 400 秒降到使用固态硬盘的 5 秒。

Bug

由于 Mono 的 Process 有问题,在调用时参数不可以过长,导致无法在参数中指定全部的 --iglob 过滤选项,因此使用 --ignore-file path/to/ignore 将忽略规则写到文件中,减少参数长度

开源项目

结合 Unity editor extension that uses spotlight on OSX for lightning fast project reference searches. Asset serialization mode should be set to “Force Text” in the editor settings. 与上述所做的 Windows 平台上的优化,创建了一个单独的项目,项目以 MIT LICENSE 发布在 GitHub 上:

networm/FindReferencesInProject2: Find asset references super fast in Unity project. Both on macOS and Windows.

Snapshot