35.性能优化-CPU-脚本-本机托管的桥接
35.1 知识点
本机-托管的桥接是什么
本机-托管的桥接(Native-Managed Bridge) 指的是 C# 的托管代码与 C/C++ 的本机代码之间的相互调用或通信机制。Unity 本身由 C++ 实现引擎内核,日常我们写的是 C# 脚本,两者之间必须有桥梁才能通信。
概念区分:
- 托管(Managed):主要指 C# 代码,执行环境为 CLR、Mono 或 IL2CPP。
- 本机(Native):主要指 C 或 C++ 代码,直接运行于 CPU,无虚拟机概念。
桥接的产生:
Unity 执行 C# 脚本时,需要让 C# 能调用到底层引擎的 C++ 函数(如渲染、物理),因此产生两个方向的桥接:
- 托管 → 本机:C# 调用 Unity 引擎内部的 C++ 函数。例如访问 GameObject、MonoBehaviour 上的各种属性和方法,通过 IL2CPP 或 Mono 的内部机制桥接到 Unity 的 C++ 层。
- 本机 → 托管:C++ 层主动调用 C# 方法。例如原生插件回调 Unity 中注册的事件,或 Android/iOS 原生代码调用 Unity 脚本方法。
总结: Unity 中的本机-托管桥接就是 C++ 与 C# 之间的调用通道。对 GameObject、Transform、Component 等的很多操作都会触发桥接,即便在 C# 里只是简单访问属性,背后也往往是跨域调用。常见会触发桥接的写法例如:
- 访问
transform.position(读/写) gameObject.AddComponent<T>()GetComponent<T>()transform.GetChild(...)gameObject.SetActive(bool)GameObject.Find(...)
下面用代码说明「读 position → 在托管侧计算 → 再写回」这类操作,每次读写都会走桥接:
// 读 position:托管 → 本机,数据从 C++ 封送到 C#
Vector3 position = transform.position;
// 在 C# 里做位置计算(纯托管,无桥接)
position += Vector3.right * speed * Time.deltaTime;
// 写回 position:托管 → 本机,数据从 C# 封送到 C++
transform.position = position;
其它典型桥接调用示例(仅作列举):
// 添加组件、获取组件、获取子物体、激活/失活、查找物体等都会触发桥接
gameObject.AddComponent<Rigidbody>();
GetComponent<Renderer>();
transform.GetChild(0);
gameObject.SetActive(false);
GameObject.Find("Player");
本机-托管的桥接带来的性能消耗
- 上下文切换:C# 调 C++ 或反向调用时,都需要堆栈转换、线程状态保存与恢复。
- 数据封送:如 Vector3 等结构体要在 C++ 与 C# 之间拷贝,读/写一次就可能封送一次。
- GC 压力:封送过程中可能产生临时对象并在托管堆上分配,增加 GC 负担。
- 调试困难:Profiler 往往只看到 C# 或 C++ 一侧,桥接本身的成本不易单独追踪。
因此要尽量减少不必要的跨域访问。
优化建议
1. 不要频繁操作 GameObject、Transform、Component 相关属性
例如对 transform.position 的多次修改应先缓存到局部变量,在托管侧算完再一次性写回,避免多次桥接。
// 推荐:读一次 → 在托管侧计算 → 写回一次
Vector3 currentPosition = transform.position;
// 在托管代码中进行位置、方向等运算
currentPosition += moveDirection * speed * Time.deltaTime;
// 计算完毕后再一次性赋值回去
transform.position = currentPosition;
2. 不要频繁调用 GetComponent<T>() 等组件查找方法
在 Awake/Start 等初始化阶段缓存引用,避免在 Update 中重复获取。
3. 不要每帧用 transform.GetChild() 遍历子物体
子物体引用应在初始化阶段获取并缓存,每帧按缓存使用。
4. 逻辑与表现分离,弱化桥接路径
游戏逻辑尽量不直接操作 GameObject,而是用纯数据结构(位置、状态等)在托管侧计算,最后集中、批量同步回 Unity 的 Transform/GameObject。
5. 使用对象池,降低桥接带来的生命周期成本
频繁 AddComponent、Instantiate 会从本机层分配新对象,桥接与生命周期开销都很大。用对象池复用 GameObject 和组件,减少创建/销毁次数。
总之:尽量降低本机-托管桥接的次数和成本——能缓存引用就缓存,减少跨域访问;需要大量位置、方向运算时,优先在托管代码中算完再统一回写。
35.2 知识点代码
Lesson35_性能优化_CPU_脚本_本机托管的桥接.cs
/*
* 本机-托管的桥接(Native-Managed Bridge):
* 指 C# 托管代码与 C/C++ 本机代码之间的相互调用或通信机制。
* Unity 引擎内核为 C++,脚本为 C#,二者通过桥接通信。
*
* 托管(Managed):C#,执行环境为 CLR、Mono、IL2CPP。
* 本机(Native):C/C++,直接运行于 CPU,无虚拟机。
*
* 桥接方向:托管→本机(C# 调引擎 C++);本机→托管(C++ 调 C# 如原生插件回调)。
* 常见触发桥接的操作:transform.position、AddComponent、GetComponent、
* GetChild、SetActive、GameObject.Find 等。
*/
public class Lesson35_性能优化_CPU_脚本_本机托管的桥接
{
#region 知识点一 本机-托管的桥接是什么
// 见正文:本机-托管桥接概念、两个方向、常见触发点及示例代码。
#endregion
#region 知识点二 本机-托管的桥接带来的性能消耗
// 1. 上下文切换:堆栈转换、线程状态保存与恢复
// 2. 数据封送:如 Vector3 在 C++ 与 C# 间拷贝
// 3. GC 压力:封送可能产生临时对象与托管堆分配
// 4. 调试困难:Profiler 仅显示一侧,桥接成本难单独追踪
#endregion
#region 知识点三 优化建议
// 1. 少频繁操作 Transform/Component 属性,先缓存再统一写回
// 2. 少在 Update 中 GetComponent,初始化阶段缓存引用
// 3. 少每帧 GetChild,子物体引用初始化时缓存
// 4. 逻辑与表现分离,用纯数据在托管侧计算再批量同步回 Unity
// 5. 用对象池复用 GameObject 与组件,减少 AddComponent、Instantiate 的桥接与生命周期成本
#endregion
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com