Unity性能优化: 性能优化之内存篇

news2024/9/22 13:42:43

前言

本文和传统的内存优化不一样,不是讲如何降低内存占用,而是讲编程开发中要注意的内存问题以及一些内存技术的演变与原理。
对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀

1: Application进程的内存分段
应用程序的内存分为: 代码段, 数据段, 栈, 堆。

代码段:
用来存放代码的二进制指令与一些常量和常量字符串, 进程启动以后划分出来,把代码指令加载到代码段,一直占用内存,并且只读不可修改。】

数据段:
用来存放代码中的静态全局变量,进程启动后,加载程序文件后,内存分配出来,并一直占用内存,直到进程结束。

栈:
函数在调用的过程中,局部变量,函数参数,函数调用时函数地址的跳转与返回执行下一条指令,这些都是使用栈来存储数据。随着函数调用返回,之前函数里面的局部变量,参数等使用的栈内存都会被回收。栈中内存如何被回收呢?其实就是一个栈顶指针,把栈顶指针往下移动,分配内存,回收内存把栈顶指针往上移动,所以局部变量是随着函数调用,在栈上反复的使用内存。如图1.1所示:


堆:

2: OS动态内存分配与手动内存管理

3:什么是内存碎片,避免内存碎片常用手段

(1)进程申请malloc 128字节,系统分配了左边第一个蓝色的块(红色块覆盖的蓝色块,那时没有红色块)。

(2)接着进程申请malloc 128字节,系统分配了左边第二个蓝色的块(没有红色块覆盖的蓝色块)

(3)进程申请释放第一个蓝色的块,free 128字节,这时候第一个蓝色块的内存空间被释放。

(4)进程申请malloc 110字节,OS从左边的地址开始分配了110大小,如红色的块所示,注意这个时候第一个蓝色块-红色块中间有个绿色块的空隙大小为18字节(128-110),可被分配,但是进程没有任何需求去用这么小的18个字节,所以就”导致内存碎片”,可用,但太小同时散落在内存空间中,无法用。

经过进程运行,一段时间后,大量的分配与释放,重新分配,有无数的”18字节”出来了,这就是内存碎片。内存碎片产生的原因其实就是系统反复大量的分配和释放大小不一的内存,导致进程要内存,系统有内存空间,但是这些空间不连续,大小满足不了申请要求,导致分配失败。

搞清楚内存碎片产生的原因,如何避免呢?大量的大小不一的内存块的申请与释放请求导致了碎片,那么解决方案就出来:

a:尽量让分配与释放的内存空间大小统一, 例如Unity ECS基于trunk来生成各种大小不同的大量Entity,每个trunk的大小是一样的。

b:对于大量分配的同一个大小的内存块,我们在进程层面做好缓存池,不还给OS,重复利用,比如物体缓存池等。

4:什么是内存泄漏,预防与追踪内存泄漏的常用方法

内存泄漏:

进程向OS malloc内存,不用后,却忘记调用free来释放,还给OS,导致系统认为这块内存进程还在用,而进程没有变量指向这块内存,无法用这块内存,可用的内存越来越少,这种情况叫”内存泄漏”。

内存泄漏会不会影响其它的进程任务运行?

进程A内存泄漏了,会不会影响进程B的可用内存越来越少呢?答案是不会,因为进程A内存泄漏,进程A中泄漏的内存不能用了,一段时间后,OS会判定 进程A泄漏的内存块对应的物理内存页长时间没有用,当物理内存吃紧的时候,会把这个物理内存页判断为”长时间不用”的物理内存页,就把泄漏对应的物理内存页的数据交换到磁盘,然后释放物理页给其它的进程B使用,所以进程A的内存泄漏,不会从根本上影响其它进程的内存使用(OS做内存页交换的开销肯定会有)。

内存泄漏的危害: 导致有内存泄漏的进程可用内存越来月少,最后导致进程无法分配内存而停止运行。

追踪内存泄漏的工具: 从系统级别给malloc与free函数,加入调试信息,运行时收集,找出来哪些地方的内存可能没有free,提示给开发者。比如Valgrind工具等,xcode也自动这种追踪工具,原理都一样。

预防内存泄漏:

(1)编写内存分配与释放函数,让大家统一来使用 这个接口来分配/释放内存,代替直接系统与库的调用。这种做法能快速的统计出是否有内存泄漏,比如怀疑有内存泄漏,那么你可以基于这个接口来加上统计信息来看,比如加上调用栈信息,看哪个地方在调用这个函数分配内存,然后查代码这个分配的在什么时候释放的,来快速的找出忘记free的对象。

(2)review代码,从代码上来review,熟悉整个内存的使用情况,来提早发现有内存泄漏的代码,毕竟等所有的项目代码都写好了再去追内存泄漏心里压力还是蛮大的。

(3)编写自动的垃圾回收机制,引入内存自动回收。现在的编程语言除了C/C++以外几乎都有基于垃圾回收的自动回收机制。

5: GC自动回收的实现原理与如何避免GC峰值冲击

基于自动垃圾回收的机制是如何实现?

这里给大家分享一种基于引用计数的垃圾回收机制的原理与实现,让你对垃圾回收器有一个全面的了解,其它的垃圾回收机制大同小异,只是判断”垃圾”的方式不一样而已。

实现一个基类我们叫做Object(现在你知道为什么很多垃圾回收的对象基类都是Object了吧,没有错,就是干这个事情的),

class Object {

int refCount; //为0表示为垃圾,增加引用,计数+1,减少引用,计数-1

重载operator=() {改变引用计数}

};

以后所有的自动回收的对象都要继承自这个Object。

接下来重载赋值操作符operator=, 当把一个变量赋值为新的Object的时候,把原来的Object引用计数减1,把新的Object的引用计数加1。

定义一个全局的内存分配器负责对象的内存分配与释放,我们暂时叫它Allocator,并提供接口 New,所有的Object的对象创建都基于这个全局的分配器来创建的,同时每分配一个对象出去,内存分配器保存好这个对象的地址或引用。当我们把对象赋值给变量的时候,老对象与新对象会触发引用计数的减加。比如:var a = xxx; a = null, 把a原来的老对象xxx引用计数-1。当没有变量指向xxx,那么它就会被定义为垃圾。

接下来就是给Allocator编写一个垃圾回收的接口叫GarbageCollection,它遍历Allocator记录的所有的分配出去的基于Object的对象,看看这些Object的引用计数是不是为0,如果是,就是垃圾,进而调用OS的释放函数回收内存,把垃圾对象回收。

编写了回收接口后什么时候调用呢?我们一般提供两种方式:手动强制回收垃圾(程序员直接调用)与特定时期的系统自动回收(系统检测,到达一些阈值条件后,调用Allocator的垃圾回收接口)。一般使用系统自动回收,这种还有一个好处,就是前面的垃圾对象还可以反复的再分配出去,比如已经是垃圾的同一类型物体,没有还给OS,但是下次分配又可以直接分配出去,可以避免内存碎片。(这样程序员只管new 对象,有家长帮你管对象了)

如何避免GC峰值冲击?

GC峰值: 某一瞬间,大量的物体的释放,导致GC花了很多时间来计算与回收垃圾,从而导致GC占用了CPU,卡住了程序。

如何避免GC峰值:可以通过平滑掉回收来避免在性能的关键时刻长时间触发GC。

举个例子,比如一个场景,有满屏的敌人,某个玩家放了一个技能,让大量的敌人瞬间全部死亡,如果在清屏的时候,触发了GC,回收大量的”敌人垃圾”,导致游戏长时间卡在了GC上,到导致帧率下降,这个就属于GC卡顿。假设GC 1万个对象卡了3秒, 我们就可以通过调节参数与阈值来避免对这1万个对象一次做回收,而是把1万个对象平滑到一个相对比较长的时间段去回收。每次只回收一部分,分多次回收。这样避免GC峰值带来的性能冲击。具体可以通过问题分析来做出相应的解决方案。

本文到此结束,很长,希望大家有所收获,关注我,一起来讨论内存相关的技术问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/377501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

maven项目无法解析插件

发现问题使用IDEA创建Maven项目时,报错无法解析插件 org.apache.maven.plugins:maven-clean-plugin这里使用的是IDEA捆绑的Maven插件解决方案查看Maven配置打开用户设置文件settings.xml,在其中加入如果该路径下没有此文件,可以自己创建一个。…

软件分析笔记02---Intermediate Representation

整体contents compiler (source code ——> machine code) non-trivial非平凡的 经过 语义分析->语法分析->类型检查等各种trivial的分析(前端),生成中间代码IR->进行non-trivial的分析(及静…

Linux 基础介绍-基础命令

文章目录01 学习目标02 Linux/Unix 操作系统简介2.1 Linux 操作系统的目标2.2 Linux 操作系统的作用2.3 Unix 家族历史2.4 Linux 家族历史2.5 Linux 和Unix 的联系2.6 Linux 内核介绍2.7 Linux 发行版本2.8 Unix/Linux 开发应用领域介绍03 Linux 目录结构3.1 Win 和Linux 文件系…

C++之入门之引用,内联函数

一、引用 1、引用的概念 在C中,引用的本质其实就是给一个已经存在的变量”起别名“。也就是说,引用与它所引用的对象共用一块空间。(同一块空间的多个名字) 就比如说,李逵又叫黑旋风,而黑旋风就是指李逵…

线程安全实例分析

一、变量的线程安全分析 成员变量和静态变量是否线程安全? ● 如果它们没有共享,则线程安全 ● 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况 —— 如果只有读操作,则线程安全 —— 如果有读写操作&am…

STM32学习笔记-USART串口通信+与野火STM32F407板载ESP8266进行通信

文章目录STM32USART介绍STM32USART框图第一部分第二部分第三部分发送器时序图接收器第四部分软件部分:STM32通过USART与板载ESP8266通讯实验板载WIFI模块电路图实现方式:第一步:配置USART1和USART3的GPIO及其中断第二步:通过中断服…

(2023版)零基础入门网络安全/Web安全,收藏这一篇就够了

由于我之前写了不少网络安全技术相关的文章和回答,不少读者朋友知道我是从事网络安全相关的工作,于是经常有人私信问我: 我刚入门网络安全,该怎么学? 要学哪些东西? 有哪些方向? 怎么选&#x…

智慧渔业海洋鱼类捕捉系统

我国的水产捕捞业可分为海洋捕捞、远洋捕捞和淡水捕捞三类,其中淡水渔业是指在淡水水域进行捕捞、养殖和加工淡水水产品的社会生产领域。近年来,随着经济水平的提高和淡水渔业的快速发展,捕捞业规模不断壮大。尽管渔业已从单纯的捕捞发展为系…

卷麻了,00后测试用例写的比我还好,简直无地自容......

经常看到无论是刚入职场的新人,还是工作了一段时间的老人,都会对编写测试用例感到困扰?例如: 如何编写测试用例? 作为一个测试新人,刚开始接触测试,对于怎么写测试用例很是头疼,无法…

Apple主推的智能家居是什么、怎么用?一篇文章带你从零完全入门 HomeKit

如果你对智能家居有所了解,那应该或多或少听人聊起过 HomeKit。由 Apple 开发并主推的的 HomeKit 既因为产品选择少、价格高而难以成为主流,又因其独特的优秀体验和「出身名门」而成为智能家居领域的焦点。HomeKit 究竟是什么?能做什么&#…

供应链的有效管理,分析指标有哪些

对于企业而言,供应链是一个很复杂的、体系化的生态系统,从原材料、到供应商、到生产、仓库、物流,最后到达经销商或者最终客户那里,这个链条很长。相关的分析指标也有很多,在这些指标里面也有非常多可以扩展、延申的内…

Android 系统的启动流程

前言:从开机的那一刻,到开机完成后launcher将所有应用进行图标展示的这个过程,大概会有哪一些操作?执行了哪些代码?作为Android开发工程师的我们,有必要好好的梳理一遍。既然要梳理Android系统的启动流程&a…

Python-scatter散点图及颜色大全

# -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as pltplt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus] False #matplotlib画图中中文显示会有问题,需要这两行设置默认字体plt.xlabel(X) plt.ylabel(Y) plt.xlim…

人工智能——离线情况下自动给视频添加字幕,支持中文,英文,日文等等

前言 最近打开百度网盘,看到播放视频有一个AI字幕功能,心情非常激动,看视频的同时可以看自动生成的字幕,防止听不清视频中人物的话语 然而不是SVIP,我试用过了之后就没有这个功能选项了 我在想,如果随便哪一个“免费”…

Windows 安装RocketMQ

文章目录一、RocketMQ是什么?二、准备工作1.环境要求2.下载与解压3.启动MQ4. 测试是否成功启动三、安装管理端1. 代码下载2. 修改配置文件3. 启动MQ客户端jar包四、rocketMQ代码的使用入门五、问题记录1. 启动mqbroker.cmd没有反应2.消费者重复消费消息一、RocketMQ…

二叉搜索树实现

树的导览 树由节点(nodes)和边(edges)构成,如下图所示。整棵树有一个最上端节点,称为根节点(root)。每个节点可以拥有具有方向的边(directed edges)&#xf…

第51天|LeetCode503.下一个更大元素 II、LeetCode42. 接雨水

1.题目链接:下一个更大元素 II 题目描述: 给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&#…

Android kotlin 组件间通讯 - LiveEventBus 及测试(更新中)

<<返回总目录 文章目录 一、LiveEventBus是什么二、测试一、LiveEventBus是什么 LiveEventBus是Android中组件间传递消息,支持AndroidX,Event:事件,Bus:总线 范围全覆盖的消息总线解决方案 进程内消息发送App内,跨进程消息发送App之间的消息发送更多特性支持 免配…

进制转换(二进制,八进制,十进制,十六进制)涵盖整数与小数部分,内容的图片全为手写【详细图解】

各种进制之间的相互转换1. 各进制表示数1.1 数码1.2 基数1.3 位权2. 十进制转换为其他进制2.1 整数部分2.2 小数部分3. 其他进制转换为十进制4. 二进制转换为八进制5. 二进制转换为十六进制6. 八进制转换为十六进制1. 各进制表示数 二进制&#xff1a;0&#xff0c;1逢二进一 八…

Java企业开发学习笔记(5下)采用注解方式使用AOP

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/FBkpc】 文章目录二、采用注解方式使用AOP2.1 创建所需自包2.2 创建杀龙任务2.3 创建勇敢骑士类2.4 创建吟游诗人切面2.5 创建Spring配置类2.6 创建骑士测试类2.7 运行测试方法testBraveKnight()&…