1.本机到托管的桥接简介
2.桥接的性能影响
3.减轻桥接性能影响的优化策略
4.桥接代码的判断
1.本机到托管的桥接
1).本机到托管的桥接简介
本机到托管的桥接表示"unity引擎底层代码(通常是c/c++)与托管代码(c#)之间的交互机制"
a.本地代码(Native Code)
- 主要是用c++编写的
- 包括unity引擎的核心部分: 渲染引擎, 物理引擎, 音频系统等
- 直接编译成机器码, 运行效率高
- 与操作系统和硬件直接交互
b.托管代码(Managed Code)
- 主要是开发者编写的c#脚本
- Unity IL2CPP模式下的C# 运行在 .net虚拟机(mono或il2cpp)
- 有垃圾回收, 内存自动管理等特性
本机代码和托管代码
a.本机代码(Native)
- 本质特性: 编译为目标平台原生机器码(无托管依赖)
- 常见载体: Unity引擎核心(C/C++)、原生插件(C/C++)、平台SDK(iOS/Android 原生代码)
- 执行环境: 直接运行在操作系统上
b.托管代码(Managed)
- 本质特性: 非直接机器码, 依赖托管运行时解释/编译
- 常见载体: Unity开发者编写的C#代码、Unity C# API
- 执行环境: 运行在 Mono虚拟机或IL2CPP托管运行时上, 有自动 GC、类型安全校验

2).为什么需要桥接

当执行CreatePrimitive时:
a.c#层: 接收你的调用
b.桥接层: 将调用和参数转换成c++能理解的形式
c.c++层: 实际执行创建网格, 设置材质, 配置渲染等复杂操作
d.桥接层: 将结果返回给c#
e.c#层: 你得到一个可以操作的GameObject引用
没有这个桥接, 你的c#代码就无法驱动底层的c++引擎
3).桥接的具体实现方式
a.Mono运行时

b.IL2CPP
- 将C# IL代码转换为C++代码
- 生成更直接的桥接代码
2.桥接的性能影响
Unity 本机 - 托管桥接存在不可避免的性能开销, 但单次开销通常较小(纳秒 / 微秒级), 高频调用会导致开销累积, 成为
性能瓶颈; 同时, 开销大小与数据类型、调用方式强相关; 用一个例子来说明两个不同公司的团队协作, 难免有额外开销,这
些开销就是桥接对性能的损耗, 具体分3点:
1).影响最大: 数据交接的"格式转换 + 复印"成本(对应: "数据封送开销")
A公司和B公司的文件格式不一样(比如A用Excel统计数据, B只认Word表格; A用中文标注, B只认英文), 要传递数据, 必须
做两件事, 这两件事都费时间:
a.格式转换: 把A的Excel转成B能看懂的Word, 把中文翻译成英文
对应: 托管代码和原生代码的数据类型/编码/内存布局不一致, 需要转换, 比如C# string(UTF-16)转C++ char*(UTF-8)
b.复印拷贝: A不能把自己的原件直接给B(怕B弄丢/改乱, 影响自己后续使用), 只能复印一份给B
对应: 托管数据不能直接给原生代码操作, 怕被破坏, 只能拷贝一份原生代码能识别的纯数据
这个"转换 + 拷贝"的时间, 就是最大的性能开销:
小数据(比如一张写着"数字5"的小纸条): 转换 + 复印几乎不费时间, 影响可以忽略
大数据(比如一本1000页的客户档案、一个装满数据的大文件夹): 转换要半天, 复印要几小时, 耗时巨大
-> 对应: 大数组、长字符串传递, 拷贝/转换开销会成为性能瓶颈
复杂数据(比如带附件、带批注的复杂报告): 转换起来更麻烦, 还容易出错, 耗时更久
-> 对应: 自定义复杂类/对象传递, 封送开销极高
2).高频累积: 来回沟通的"跑腿"成本(对应: "上下文切换开销")
A公司员工要找B公司员工办事, 不能直接隔空传话, 必须走流程:
a.放下自己手里的活记好自己做到哪一步了(比如"我刚整理完第3页档案", 对应: 保存当前代码的执行状态)
跑到B公司(对应: 切换到原生代码执行环境)
b.办完事, 再跑回A公司, 回忆自己之前做到哪了, 继续干活(对应: 恢复托管代码的执行状态)
单次跑腿的时间很短(比如: 1分钟)但架不住次数多
- 一天跑1次: 几乎不影响工作(对应: 低频桥接调用, 比如游戏启动时调用1次原生插件初始化)
- 一天跑1000次: 光跑腿就花了1000分钟, 根本没时间干正事
对应: Update/FixedUpdate每帧调用上千次桥接函数, 跑腿成本累积, 导致帧率下降、程序卡顿
3).额外隐患: 协作产生的"垃圾没人清" (对应: GC压力 + 内存泄漏)
A和B协作时, 会产生一些临时垃圾
a.临时复印纸: 交接数据时, 打印的临时拷贝(对应: 桥接时创建的临时托管对象, 比如数组副本、字符串包装器)堆在办公
室没人清, 越堆越多, 最后需要花时间集中打扫(对应: GC回收, 打扫时所有人都要停工, 就是GC卡顿)
b.废弃文件没扔: 有些复印纸/文件用完后, 随手扔在走廊里
-> 对应: 手动分配的原生内存没释放, 比如Marshal.AllocHGlobal分配的内存没Free
越积越多, 最后走廊都堵死了
-> 对应: 内存泄漏, 程序占用内存越来越高, 最终崩溃
3.减轻桥接性能影响的优化策略
1).减少跨边界调用次数(核心优化)
批量处理数据: 将"高频单次调用"改为"低频批量调用", 比如每帧不是调用1000次获取单个数据, 而是1次调用获取1000条批
量数据, 大幅减少上下文切换次数
避免热路径调用: 不在Update、FixedUpdate、LateUpdate等每帧执行的热路径中进行桥接调用; 若必须调用,尽量降低调用
频率(如每10帧调用1次)

2).优化数据封送(降低核心开销)
a.优先使用Blittable类型: 尽量用int、float、byte、 blittable结构体(仅包含 Blittable 类型的结构体)作为参数/返
回值, 避免非Blittable类型和引用类型
b.手动控制内存, 避免拷贝
使用fixed关键字固定托管数组内存, 直接传递指针给本机代码(无需拷贝数组)
c.复用非托管内存缓冲区: 提前分配固定大小的本机内存, 重复使用, 避免频繁申请/释放
d.避免字符串传递: 尽量用byte[](UTF-8)代替string传递文本数据, 减少编码转换和内存拷贝开销
3).减少GC压力与内存泄漏
a.手动释放非托管内存: 使用Marshal.AllocHGlobal分配的内存, 必须用Marshal.FreeHGlobal释放; 原生插件分配的内存
需提供释放接口并在C#中调用
b.避免临时对象创建: 复用托管对象/缓冲区, 减少桥接过程中临时对象的生成, 降低GC触发频率
4.桥接代码的判断
1).很多Unity公开的C#API, 底层其实是C++引擎核心实现的, C#调用这些API时, 会隐式触发桥接
using UnityEngine;
public class ImplicitBridgeTest : MonoBehaviour
{
void Update()
{
transform.position += new Vector3(0, 0.1f, 0);
if (Input.GetKeyDown(KeyCode.Space))
{
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/test.ab");
GameObject prefab = ab.LoadAsset<GameObject>("TestObj");
Instantiate(prefab);
ab.Unload(false);
}
GetComponent<Renderer>().material.color = Color.red;
}
}
2).在iOS/Android移动端开发中, Unity常需要与平台原生代码交互(如获取设备信息、调用原生支付 / 分享), 这种交互必
然触发桥接
a.iOS原生代码是Objective-C(基于C/C++), Unity通过UnitySendMessage或自定义桥接接口交互
b.Android原生代码是Java, 底层通过JNI(Java Native Interface)与C/C++交互, Unity与Android原生交互时, 会触发多
层桥接(C# → Java → C++)