unity内存管理和优化 笔记
https://www.bilibili.com/video/BV1sibVzTEE5/?spm_id_from=333.1007.tianma.2-2-5.click&vd_source=436e1882d94925de09cd95044b65f90a
CPU怎么访问内存

通过缓存来提升访问速度
访问缓存是用字为单位,就是64位,带宽比较小,CPU先从缓存中找数据,速度快
如果直接访问内存,就是用块来访问,带宽较大。速度慢
字是CPU一次能处理的基本数据单元的大小,和CPU的架构有关,一个32位的CPU,一个字的大小就是32位,就是4个字节,一个64位的CPU,一个字的大小就是8个字节
块一般指的是缓存行,是处理器设计时确定的固定值,现在一般是64个字节,远比一个字要大
缓存快是因为 SRAM + 物理集成/靠近 CPU;内存慢是因为 DRAM + 物理分离/远 + 需要刷新。这是硬件本质决定的访问延迟差异。

答案是左边,因为访问内存是用缓存行的方式,因此先行后列的访问数组,比较符合数组的内存分配,会先抓取一个行,然后再依次在这个行里面找对应列的数据

右边更省,和内存对齐有关系

unity中的ECS架构使用结构体,放在连续的非托管堆的内存空间,实现高效内存访问,也是和这个有关
操作系统如何管理内存
内存分为  物理内存  虚拟内存
CPU只能访问物理内存
PC端和移动端的架构区别



unity如何使用内存

native 内存一般是unity引擎底层分配的内存
托管的内存一般是我们写C#代码产生的内存
要监测游戏性能最好在runtime之下进行处理,就是打包环境下
native 内存出问题是个非常大的麻烦,一定要注意
栈 和 堆



看不懂这张图
基于unity,C#来说

这里好像有个错误,结构体最终会分配在栈上还是堆上,取决于 他是在哪里定义的,如果他定义在一个类里面作为一个成员,那么它也一样会分配在堆内存


这些内存的分配是由各种分配器来做的
以下是unity中各个类型的分配器


如果中间的user块内存释放了,上面的内存也不会移动下来,因为不知道上面的内存块大小,并且可能上面有很多块,没法一一移动,所以一般不会去管,因此栈内存分配一般只分配临时的快速的小型的内存。
这里的user块有个头节点,里面有个标记,来标记是否使用中,有一个块的大小size,有个指针,指向下一个和上一个。
那为什么释放某块内存,不直接改指针呢?
我问了deepseek,说unity的栈分配器就是简单的一个栈,一段连续的内存空间,里面分的块绝对不会有标记,大小,指针。。。。我麻了。。。
第二个问题,释放栈顶内存之后,肯定是往下依次检查是否已经有未使用的内存块,依次释放,直到头指针指向仍在使用的块上

托管内存

两种编译C#的虚拟机

两个案例

蓝色是项目内存,绿色是unity分配的内存,多出来的部分是空闲内存
这里的内存一直在增长,几乎没有释放,同时unity找操作系统分配的内存也一直在增长中
在这个过程中涉及两个虚拟机对内存的处理,mono虚拟机,在向操作系统申请内存后,一般不会还回去。而il2cpp则会在垃圾回收的时候,如果某块内存连续6次都没有垃圾可回收是空闲状态,才会还给操作系统,但是这种情况非常的少见,所以绿色部分内存一般都是只会增长

这个问题是unity找操作系统分配的内存在某个时间点突然升高,但是项目内存其实并没有那么高
这个原因是项目的内存在之前不断申请释放的过程中产生了很多内存碎片,导致再申请某个大的内存的时候,没有找到合适的内存,因为unity只能找操作系统申请一块大内存。
需要经验在做系统架构的时候避免这种问题
垃圾收集算法
主流算法:
1.Mark-Swipe
标记-清理
主要是boehmGC 和 increamentGC 用的这种,unity基本也是依赖这两个方式
2,SGen
java主要用这种,对内存碎片比较友好,会把空闲内存合并
3.Refrence
引用计数
C++,Pathon用的这种算法
                    
                
                
            
        
浙公网安备 33010602011771号