为您推荐:
精华内容
最热下载
问答
  • 10.88MB guoruibin123 2021-04-24 08:52:12
  • 优先考虑这两个方面优化。 1. 优化数据库表结构的设计 为什么数据库表的设计会影响性能? 字段的数据类型:不同的数据类型的存储和检索方式不同,对应的性能也不同,所以说要合理的选用字段的数据类型。 ...

    一:简介

    MySQL性能优化是通过优化各个方面的,不仅仅是优化SQL语句这一方面,而是通过各个方面的优化,每个地方优化一些,这样整体性能就会有明显的提升。

     

    二:优化方式

     

    主要的优化是SQL的优化和表结构的优化,这里可以起到数据库优化的80%的作用。

    优先考虑这两个方面的优化。

     

    1. 优化数据库表结构的设计

    为什么数据库表的设计会影响性能?

    字段的数据类型:不同的数据类型的存储和检索方式不同,对应的性能也不同,所以说要合理的选用字段的数据类型。

    比如人的年龄用无符号的unsigned tinyint即可,没必要用integer数据类型的长度:数据库最终要写到磁盘上,

    所以字段的长度也会影响着磁盘的I/O操作,如果字段的长度很大,那么读取数据也需要更多的I/O, 所以合理的字段长度也能提升数据库的性能。

    比如用户的手机号11位长度,没必要用255个长度。

    表的存储引擎:常用的存储引擎有MyISAM、InnoDB、Memory,不同的存储引擎拥有不同的特性,所以要合理的利用每种存储引擎的长处和优点来提供数据的性能。

    MyISAM不支持事务,表级锁,但是查询速度快,InnoDB支持事务,行锁。

     

    2. SQL优化

    MySQL性能优化的一个很重要的手段就是对SQL语句的优化。其中最重要的方式就是使用索引。

    还有一些其他的优化SQL的小技巧,比如用join代替子查询之类的,后续再总结。

    hash join、merge join之类的。

     

    以下部分是次要优化的点,一般是DBA或者架构师需要去做的事情。

    3. 分表

    当一个表的数据量很大的时候,查询就变的很慢,所以减少表里的记录的数量是优化的一种方式,这种方式就是将一张表的数据拆分成多张表,这样每张表的数量就减少了,这样查询速度就相对来说就快了一些。

    大表对DDL操作有一定的影响,如创建索引,添加字段 修改表结构需要长时间锁表,会造成长时间的主从延迟,影响正常的数据操作

    4. 大事务

    大事务:运行时间比较长,操作的数据比较多的事务 风险:锁定太多的数据,造成大量的阻塞和锁超时,回滚时所需时间比较长,执行时间长容易造成主从延迟

    避免一次处理太多的数据,移除不必要在事务中的select操作

    5. 数据库参数配置优化(很重要)

    mysql是一个高度定制化的数据库系统,提供了很多配置参数(如最大连接数、数据库占用的内存等),这些参数都有默认值,一般默认值都不是最佳的配置,一般都需要根据应用程序的特性和硬件情况对mysql的配置进行调整。

    例如最大连接数默认为100,即使SQL语句优化的再好,硬件设备配置再高,当请求超过100时都要再等待,这就是配置不合理导致MySQL不能发挥它的最大能力。

    6. 主从复制,读写分离

    一台MySQL服务器同一时间点支持的并发数是有限的,当大量并发(如秒杀活动等,很多用户都同一时刻访问数据库)时,一台数据库处理不过来,所以增加MySQL服务器的数量也是一种增强数据库性能的方式。

    通过使用MySQL主从复制,增删改操作走Master主服务器,查询走Slaver从服务器,这样就减少了只有一台MySQL服务器的压力。

    7. 增加缓存层

    减少数据库连接也是一种优化手段,有些查询可以不用访问数据库,可以通过使用缓存服务器如redis、memcache、elasticsearch等增加缓存,减少数据库的连接

    8. 升级服务器硬件

    当所有优化手段都用了,性能仍需要优化,那么只有升级MySQL服务器端硬件了,更快的磁盘IO设备,更强的CPU,更大的内存,更大的网卡流量(带宽)等。

     

    总之对MySQL性能的提升,是通过各个方面来提升的,每个方面都提升一点,整体加起来就有明显的提升。

     

    参考链接:https://baijiahao.baidu.com/s?id=1660232228855199630&wfr=spider&for=pc

    展开全文
    kevin1993best 2021-03-01 22:49:55
  • 模型的准确率不高,模型的metrics达不到预期,常见的原因有以下几方面: 1、数据集问题 数据集中缺失值过多 数据集每类别的样本数目不均衡 数据集中存在异常值 数据集中的数据对预测结果帮助不大(例如使用...

    本文转载自 https://zhuanlan.zhihu.com/p/353715732

    模型准确率不高的常见原因

    模型的准确率不高,模型的metrics达不到预期,常见的原因有以下几方面:

    1、数据集问题

    • 数据集中缺失值过多
    • 数据集每个类别的样本数目不均衡
    • 数据集中存在异常值
    • 数据集中的数据对预测结果帮助不大(例如使用年龄预测性别)

    2、数据处理算法设计和实现问题

    • 数据处理参数有误
    • 未对数据进行归一化
    • 特征提取算法(如果使用了)存在错误
    • train和validation数据处理方式不一致

    3、算法设计和实现问题

    • API使用错误
    1. 没有遵循深度学习框架约束
    2. 算子使用错误
    • 计算图结构错误
    1. 权重共享错误
    2. 权重冻结错误(例如忘记冻结应冻结的权重)
    3. 节点连接错误(例如应连接在图中的节点未连接)
    • loss函数错误
    • 优化器错误
    • 权重初始值错误
    • 算法本身设计有缺陷导致精度无法达到预期。

    4、超参设置问题

    5、普通python编程错误

    6、环境问题

    • 依赖软件问题
    • 环境变量配置问题
    • 云上环境问题

    问题分析整体思路

    分析模型准确率不高的问题,就是要分析数据集、数据处理、算法设计、算法实现等方面是否存在问题。这一过程是有章可循的,建议参考下面五步,逐步定位模型表现问题:

    第一步 检查代码和超参

    第二步 检查模型结构

    第三步 检查输入数据

    第四步 检查loss曲线

    第五步 检查准确率是否达到预期

    代码是准确率问题的重要源头,检查代码重在对脚本和代码做检查,力争在源头发现问题;模型脚本体现了AI模型在AI框架上的表达和映射,检查模型脚本重在检查脚本表达和算法工程师的设计是否一致;有的问题要到动态的训练过程中才会发现,检查输入数据和loss曲线正是将代码和动态训练现象结合进行检查;检查准确率是否达到预期则是对整体分析过程的重新审视。此外,充分准备,熟悉模型也是很重要的。下面将分别介绍这些思路。

    模型问题分析准备

    1、回顾算法设计,全面熟悉模型

    分析前,要先对算法设计做回顾,确保算法设计明确。如果参考论文实现模型,则应回顾论文中的全部设计细节和超参选择情况;如果参考其它脚本实现模型,则应确保有一个唯一的、精度能够达标的标杆脚本;如果是新开发的算法,也应将重要的设计细节、超参选择、参考论文和脚本明确出来。这些信息是后面检查脚本步骤的重要依据。

    分析问题前,还要全面熟悉模型。只有熟悉了模型,才能准确理解各种现象中蕴含的信息,判断是否存在问题,查找问题源头。因此,花时间理解模型算法和结构、理解模型中算子的作用和参数的含义、理解模型所用优化器的特性是很重要的。动手分析问题细节前,建议先带着问题加深对这些模型要素的了解。

    2、使用小数据集测试目标脚本能否成功拟合

    若模型不能拟合(fit)小数据集,说明模型本身的设计存在问题。

     

    第一步 检查代码和超参

    代码是模型问题的重要源头,超参问题、模型结构问题、数据问题、算法设计和实现问题会体现在脚本中,对脚本做检查是定位模型问题很有效率的手段。

    检查代码主要依赖代码走读和单元测试。代码走读建议使用小黄鸭调试法:在代码走读的过程中,耐心地向没有经验的“小黄鸭”解释每一行代码的作用,从而激发灵感,发现代码问题。检查脚本时,要注意检查脚本实现(包括数据处理、模型结构、loss函数、优化器等实现)同设计是否一致,如果参考了其它脚本,要重点检查脚本实现同其它脚本是否一致,所有不一致的地方都应该有充分合理的理由,否则就应修改。

    还可以通过编写单元测试的方法检查脚本,将脚本在逻辑上拆分为多个模块或函数,然后编写单元测试,依次对这些模块和函数进行验证。

    检查脚本时,也要关注超参的情况,超参问题主要体现为超参取值不合理,例如:

    1. 学习率设置不合理;
    2. loss_scale参数不合理;
    3. 权重初始化参数不合理等。

    第二步 检查模型结构

    在模型结构方面,常见的问题有:

    1. 算子使用错误(使用的算子不适用于目标场景,如应该使用浮点除,错误地使用了整数除);
    2. 权重共享错误(共享了不应共享的权重);
    3. 权重冻结错误(冻结了不应冻结的权重);
    4. 节点连接错误(应该连接到计算图中的block未连接);
    5. loss函数错误;
    6. 优化器算法错误(如果自行实现了优化器)等。

    建议通过检查模型代码的方式对模型结构进行检查。此外,还可以借助各种计算图可视化工具检查计算图。若有标杆脚本,还可以同标杆脚本对照查看计算图,检查当前脚本和标杆脚本的计算图是否存在重要的差异。

    通过计算图可视化检查模型结构-来自MindSpore的可视化工具MindInsight

    考虑到模型结构一般都很复杂,期望在这一步就能发现所有的模型结构问题是不现实的。只要通过可视化的模型结构加深对计算图的理解,发现明显的结构问题即可。后面的步骤中,发现了更明确的问题现象后,我们还会回到这一步重新检查确认。

    第三步 检查输入数据

    通过检查输入模型的数据,可以结合脚本判断数据处理流水线和数据集是否存在问题。输入数据的常见问题有:

    1. 数据缺失值过多;
    2. 每个类别中的样本数目不均衡;
    3. 数据中存在异常值;
    4. 数据标签错误;
    5. 训练样本不足;
    6. 未对数据进行标准化,输入模型的数据不在正确的范围内;
    7. finetune和pretrain的数据处理方式不同;
    8. 训练阶段和推理阶段的数据处理方式不同;
    9. 数据处理参数不正确等。

    建议借助工具可视化输入数据,若数据明显不符合预期(例如数据被裁剪的范围过大,数据旋转的角度过大等),可以判断输入数据出现了一定的问题。

    可视化查看输入模型的数据

    可视化查看数据处理流水线--来自MindSpore的可视化工具MindInsight

    如果有标杆脚本,还可以同标杆脚本对照,检查数据处理流水线输出的数据是否和标杆脚本的数据相同。例如,将数据处理流水线输出的数据保存为npy文件,然后使用numpy.allclose()方法对标杆脚本和当前脚本的数据进行对比。如果发现不同,则数据处理阶段可能存在精度问题。

    若数据处理流水线未发现问题,可以手动检查数据集是否存在分类不均衡、标签匹配错误、缺失值过多等问题。

    第四步 检查loss曲线

    很多模型问题会在网络训练过程中通过观察loss曲线发现,常见的问题或现象有:

    1. 权重初始化不合理(例如初始值为0,初始值范围不合理等);
    2. 权重中存在过大、过小值;
    3. 权重变化过大;
    4. 权重冻结不正确;
    5. 权重共享不正确;
    6. 激活值饱和或过弱(例如Sigmoid的输出接近1,Relu的输出全为0);
    7. 梯度爆炸、消失;
    8. 训练epoch不足;
    9. 算子计算结果存在NAN、INF;
    10. 算子计算过程溢出(计算过程中的溢出不一定都是有害的)等。

    loss曲线能够反映网络训练的动态趋势,通过观察loss曲线,可以得到模型是否收敛、是否过拟合等信息。loss跑飞和loss收敛慢是模型准确率不高时的主要loss现象。当loss跑飞时,模型的准确率可能只有随机猜测的水平。当loss收敛慢时,若epoch不够多,loss始终也无法收敛到预期值,模型准确率将无法达到预期。

    可视化查看loss曲线。图中展示了训练集上的loss随训练迭代数的变化情况。

    部分问题(例如错误地冻结了不该冻结的权重)难以直接通过loss曲线观察出来,建议同时查看参数分布图,观察模型参数的变化情况,可以得到权重是否更新过快、过慢,权重分布范围是否正常等信息。

    可视化查看训练过程中的权重变化情况。图中为conv1卷积的权重分布随训练迭代数的变化情况。

     

    更进一步地,还可以使用深度学习领域的调试器,例如MindSpore框架的调试器(https://mindspore.cn/tutorial/training/zh-CN/master/advanced_use/debugger.html )等工具,对训练现场进行检查,更准确地发现问题。

    定位loss跑飞问题

    loss跑飞是指loss中出现了NAN、+/-INF或者特别大的值。loss跑飞一般意味着算法设计或实现存在问题。定位思路如下:

    1. 回顾脚本、模型结构和数据:检查超参是否有不合理的特别大/特别小的取值,检查模型结构是否实现正确,特别是检查loss函数是否实现正确,检查输入数据中是否有缺失值、是否有特别大/特别小的取值。
    2. 观察参数分布图,检查参数更新是否有明显的异常。若发现参数更新异常,可以结合调试器定位参数更新异常的原因。
    3. 使用调试器对训练现场进行检查。
    • 若loss值出现NAN、+/-INF,可使用“检查张量溢出”条件添加全局监测点,定位首先出现NAN、+/-INF的算子节点,检查算子的输入数据是否会导致计算异常(例如除零)。若是算子输入数据的问题,则可以针对性地加入小数值epsilon避免计算异常。
    • 若loss值出现特别大的值,可使用“检查过大张量”条件添加全局监测点,定位首先出现大值的算子节点,检查算子的输入数据是否会导致计算异常。若输入数据本身存在异常,则可以继续向上追踪产生该输入数据的算子,直到定位出具体原因。
    • 若怀疑参数更新、梯度等方面存在异常,可使用“检查权重变化过大”、“检查梯度消失”、“检查梯度过大”等条件设置监测点,定位到异常的权重或梯度,然后结合张量检查视图,逐层向上对可疑的正向算子、反向算子、优化器算子等进行检查。

    定位loss收敛慢问题

    loss收敛慢是指loss震荡、收敛速度慢,经过很长时间才能达到预期值,或者最终也无法收敛到预期值。相较于loss跑飞,loss收敛慢的数值特征不明显,更难定位。定位思路如下:

    1、回顾脚本、模型结构和数据,

    • 检查超参是否有不合理的特别大/特别小的取值,特别是检查学习率是否设置过小或过大,学习率设置过小会导致收敛速度慢,学习率设置过大会导致loss震荡、不下降;
    • 检查模型结构是否实现正确,特别是检查loss函数、优化器是否实现正确;
    • 检查输入数据的范围是否正常,特别是输入数据的值是否过小

    2、观察训练看板中的参数分布图,检查参数更新是否有明显的异常。若发现参数更新异常,可以结合调试器定位参数更新异常的原因。

    3、使用调试器模块对训练现场进程检查。

    • 可使用“检查权重变化过小”、“检查未变化权重”条件对可训练(未固定)的权重进行监测,检查权重是否变化过小。若发现权重变化过小,可进一步检查学习率取值是否过小、优化器算法是否正确实现、梯度是否消失,并做针对性的修复。
    • 可使用“检查梯度消失”条件对梯度进行监测,检查是否存在梯度消失的现象。若发现梯度消失,可进一步向上检查导致梯度消失的原因。例如,可以通过“检查激活值范围”条件检查是否出现了激活值饱和、Relu输出为0等问题。

    4、若认为loss的收敛速度正常,可以尝试增加epoch数目继续训练,看epoch数目增加后,loss能否收敛到预期值。

    第五步 检查准确率是否达到预期

    1、检查训练集准确率

    若训练集上loss已收敛,但是训练集上的准确率未达到预期,首先应重点检查loss函数是否实现正确,使用前文介绍的方法走读代码、编写单元测试对loss函数进行检查。必要时重新进行前四步,回顾代码、模型结构、输入数据和loss曲线:

    1. 检查脚本,检查超参是否有不合理的值
    2. 检查模型结构是否实现正确
    3. 检查输入数据是否正确
    4. 检查loss曲线的收敛结果和收敛趋势是否存在异常

    若未发现问题,则应考虑优化超参取值。若多组超参均没有好的效果,则应考虑优化模型结构和算法,尝试新的idea。

    2、检查验证集准确率

    若训练集准确率和验证集准确率都未达到预期,则应首先参考上一节检查训练集准确率。若训练集准确率已达到预期,但是验证集准确率未达到预期,大概率是模型出现了过拟合,处理思路如下:

    1. 检查验证集评估脚本的评估逻辑有无错误。特别是数据处理方式是否与训练集一致,推理算法有误错误,是否加载了正确的模型checkpoint。
    2. 增加数据量。包括增加样本量,进行数据增强和扰动等。
    3. 正则化。常见的技术如参数范数惩罚(例如向目标函数中添加一个正则项),参数共享(强迫模型的两个组件共享相同的参数值),提前中止训练等。
    4. 适当降低模型的规模。例如减少卷积层数等。

    3、检查测试集准确率

    若验证集和测试集准确率都未达到预期,则应首先参考上一节检查验证集准确率。若验证集准确率已达到预期,但是测试集准确率未达到预期,考虑到测试集的数据是模型从未见过的新数据,原因一般是测试集的数据分布和训练集的数据分布不一致。处理思路如下:

    1. 检查测试集评估脚本的评估逻辑有误错误。特别是数据处理方式是否与训练集一致,推理算法有误错误,是否加载了正确的模型checkpoint。
    2. 检查测试集中的数据质量,例如数据的分布范围是否明显同训练集不同,数据是否存在大量的噪声、缺失值或异常值。

     

    小结

    准确率低等问题的分析有章可循,首先检查代码和超参,然后检查模型结构,检查输入数据,检查loss曲线,最后确认准确率达到预期。由于准确率低可能存在多个原因,每一步中的问题修复后,都要重新执行训练和评估,确认是否还有其它的潜在问题。希望这些分析思路能起到良好的引导的作用,帮助你训练出满意的模型。

     

     

    注1:部分调试器检查功能仅在MindSpore调试器(https://mindspore.cn/tutorial/training/zh-CN/master/advanced_use/debugger.html )中可用。

    注2:文中截图全部来自MindSpore可视化调试调优工具MindInsight(https://mindspore.cn/tutorial/training/zh-CN/master/advanced_use/visualization_tutorials.html )。

     

    展开全文
    archimekai 2021-03-02 14:34:24
  • TPE CMAES 网格搜索 随机搜索 贝叶斯优化用贝叶斯优化进行超参数调优@QI ZHANG · JUL 12, 2019 · 7 MIN READ超参数调优一直是机器学习里比较intractable的问题,繁多的超参数以及指数型爆炸的参数空间,往往让人...

    TPE CMAES 网格搜索 随机搜索 贝叶斯优化

    用贝叶斯优化进行超参数调优

    @QI ZHANG · JUL 12, 2019 · 7 MIN READ

    超参数调优一直是机器学习里比较intractable的问题,繁多的超参数以及指数型爆炸的参数空间,往往让人无从下手。调参是一个很枯燥的过程,而且最后也不一定有很好的reward。很多的机器学习工程师也会戏称自己是”调参民工”,”炼丹师”……

    超参数(Hyper-parameters):Hyper-parameters are parameters that are not directly learnt within estimators.

    超参数的优化可以看做是这样一个方程:

    ???=argmin??∈??(??)x?=arg?minx∈Xf(x)

    其中??(??)f(x) 表示目标函数,用于衡量误差,可以是MSE, RMSE, MAE等(如果是accuracy等指标可以将其添加负号求最小值),???x?是使得??(??)f(x)最小的参数组合,理论上我们的目标就是要找到这个???x?,但是要找到这样一个全局最优解几乎是不可能的。首先这个??(??)f(x)是个黑盒子,我们没法直接进行优化求解。事实上,我们每次为了得到??(??)f(x),需要经过模型训练和评估,非常耗时,尤其对于一些深度学习模型,这个过程会特别漫长。我们只能在有限的计算资源和时间内,得到一些相对的局部最优解。

    一般的调参方法有下面几种:

    手动调参(Manual Search)

    网格搜索(Grid Search)

    随机搜索(Randomized Search)

    贝叶斯优化(Bayesian Optimization)

    1. 手动调参

    对于手动调参,会对模型最重要的一些参数基于经验进行调节。比如lightgbm的叶num_leaves, learning_rate, feature_fraction, lambda_l1,lambda_l2, min_data_in_leaf等。

    2. Randomized Search vs Grid Search

    Grid Search会对定义的参数空间进行暴力搜索。网格搜索速度慢,但在搜索整个搜索空间方面效果很好。Randomized Search是从定义的参数空间中进行采样,然后训练。随机搜索很快,但可能会错过搜索空间中的重要点。

    事实上,调参的时候,不需要遍历模型的所有参数。事实上,影响效果的往往只有其中的几个参数,一般对这些参数进行Randomized Search或者Grid Search即可。具体可以查看模型文档,或相关文献。

    # Comparing randomized search and grid search for hyperparameter estimation

    import numpy as np

    from time import time

    from scipy.stats import randint as sp_randint

    from sklearn.model_selection import GridSearchCV

    from sklearn.model_selection import RandomizedSearchCV

    from sklearn.datasets import load_digits

    from sklearn.ensemble import RandomForestClassifier

    # get some data

    digits = load_digits()

    X, y = digits.data, digits.target

    # build a classifier

    clf = RandomForestClassifier(n_estimators=20)

    # Utility function to report best scores

    def report(results, n_top=3):

    for i in range(1, n_top + 1):

    candidates = np.flatnonzero(results[‘rank_test_score‘] == i)

    for candidate in candidates:

    print("Model with rank: {0}".format(i))

    print("Mean validation score: {0:.3f} (std: {1:.3f})".format(

    results[‘mean_test_score‘][candidate],

    results[‘std_test_score‘][candidate]))

    print("Parameters: {0}".format(results[‘params‘][candidate]))

    print("")

    # specify parameters and distributions to sample from

    param_dist = {"max_depth": [3, None],

    "max_features": sp_randint(1, 11),

    "min_samples_split": sp_randint(2, 11),

    "bootstrap": [True, False],

    "criterion": ["gini", "entropy"]}

    # run randomized search

    n_iter_search = 20

    random_search = RandomizedSearchCV(clf, param_distributions=param_dist,

    n_iter=n_iter_search, cv=5)

    start = time()

    random_search.fit(X, y)

    print("RandomizedSearchCV took %.2f seconds for %d candidates"

    " parameter settings." % ((time() - start), n_iter_search))

    report(random_search.cv_results_)

    # use a full grid over all parameters

    param_grid = {"max_depth": [3, None],

    "max_features": [1, 3, 10],

    "min_samples_split": [2, 3, 10],

    "bootstrap": [True, False],

    "criterion": ["gini", "entropy"]}

    # run grid search

    grid_search = GridSearchCV(clf, param_grid=param_grid, cv=5)

    start = time()

    grid_search.fit(X, y)

    print("GridSearchCV took %.2f seconds for %d candidate parameter settings."

    % (time() - start, len(grid_search.cv_results_[‘params‘])))

    report(grid_search.cv_results_)

    Output:

    RandomizedSearchCV took 5.55 seconds for 20 candidates parameter settings.

    Model with rank: 1

    Mean validation score: 0.939 (std: 0.024)

    Parameters: {‘bootstrap‘: False, ‘criterion‘: ‘entropy‘, ‘max_depth‘: None, ‘max_features‘: 7, ‘min_samples_split‘: 3}

    Model with rank: 2

    Mean validation score: 0.933 (std: 0.022)

    Parameters: {‘bootstrap‘: False, ‘criterion‘: ‘gini‘, ‘max_depth‘: None, ‘max_features‘: 6, ‘min_samples_split‘: 6}

    Model with rank: 3

    Mean validation score: 0.930 (std: 0.031)

    Parameters: {‘bootstrap‘: True, ‘criterion‘: ‘gini‘, ‘max_depth‘: None, ‘max_features‘: 6, ‘min_samples_split‘: 6}

    GridSearchCV took 16.95 seconds for 72 candidate parameter settings.

    Model with rank: 1

    Mean validation score: 0.937 (std: 0.019)

    Parameters: {‘bootstrap‘: False, ‘criterion‘: ‘entropy‘, ‘max_depth‘: None, ‘max_features‘: 10, ‘min_samples_split‘: 2}

    Model with rank: 2

    Mean validation score: 0.935 (std: 0.020)

    Parameters: {‘bootstrap‘: False, ‘criterion‘: ‘gini‘, ‘max_depth‘: None, ‘max_features‘: 10, ‘min_samples_split‘: 2}

    Model with rank: 3

    Mean validation score: 0.930 (std: 0.029)

    Parameters: {‘bootstrap‘: False, ‘criterion‘: ‘entropy‘, ‘max_depth‘: None, ‘max_features‘: 10, ‘min_samples_split‘: 3}

    1.1 Grid Search

    from sklearn.model_selection import GridSearchCV

    def func_grid_search(model, X_train, y_train, param_grid, cv=5, n_jobs=-1):

    """

    model: model instance

    para_grid: dict type, params searching grid.

    para_grid = {‘n_estimators‘:[100,200,500],

    ‘max_depth‘: [5, 8, 10, 15],

    ‘max_features‘: [0.80, 0.90, 0.95],

    ‘min_samples_split‘:[2, 5, 8],

    ‘min_samples_leaf‘: [1, 3, 5]}

    """

    gs = GridSearchCV(model, param_grid, cv=cv, n_jobs=n_jobs)

    gs.fit(X_train, y_train.ravel())

    print(gs.best_params_)

    print(gs.best_score_)

    # print(gs.cv_results_)

    bst_model = gs.best_estimator_

    return bst_model

    1.2 Randomized Search

    from sklearn.model_selection import RandomizedSearchCV

    from scipy.stats import randint as sp_randint

    def func_random_search(model, X_train, y_train, param_dist, n_iter=20, cv=5, n_jobs=-1):

    """

    # parameters for GridSearchCV

    # specify parameters and distributions to sample from

    param_dist = {"max_depth": [3, 5],

    "max_features": sp_randint(1, 11),

    "min_samples_split": sp_randint(2, 11),

    "min_samples_leaf": sp_randint(1, 11),

    "bootstrap": [True, False]

    }

    """

    rs = RandomizedSearchCV(model, param_dist, n_iter=n_iter, cv=cv, n_jobs=n_jobs)

    rs.fit(X_train, y_train.ravel())

    print(rs.best_params_)

    print(rs.best_score_)

    # print(rs.cv_results_)

    bst_model = rs.best_estimator_

    return bst_model

    Grid Search太慢了,并不是很实用,一般会对参数空间先调一个粗粒度的格点搜索,然后根据结果进行细粒度的调整。而Randomized Search在迭代一定的次之后也可能实现较好的效果,值得更多的尝试。

    3. Bayesian Optimization

    Grid Search和Randomized Search虽然可以让整个调参过程自动化,但它们无法从之前的调参结果中获取信息,可能会尝试很多无效的参数空间。而贝叶斯优化,会对上一次的评估结果进行追踪,建立一个概率模型,反应超参数在目标函数上表现的概率分布,即??(score|hyperparameters)P(score|hyperparameters),可用于指导下一次的参数选择。

    贝叶斯优化可以更好的trade off Exploration&Exploitation,而且适用于随机、非凸、不连续方程的优化。具体过程可用一句话概括为:对目标函数建立概率模型,通过这个概率模型得到最promising的参数组合,用于最终目标函数的评估。

    Sequential Model-Based Optimization(SMBO) 是贝叶斯优化更具体的表现形式,可认为它们是等价的。一般会有以下几个过程:

    给定要搜索的超参数空间

    定义一个目标函数用于评估优化

    建立目标函数的surrogate model

    建立一个评估surrogate model,作为选择超参数的标准(选择方程)

    获取score和hyperparameter的样本用于更新 surrogate model

    贝叶斯优化模型主要的区分是代理函数(surrogate function)的差异。surrogate model一般有 Gaussian Process, Random Forest和Tree Parzen Estimator (TPE) 这几种。常见的框架有Spearmint, Hyperopt, SMAC, MOE, BayesianOptimization, skopt等,它们的对比如下表:

    librarysurrogate function

    Spearmint

    Gaussian Process

    Hyperopt

    Tree Parzen Estimator (TPE)

    SMAC

    Random Forest

    Optunity包括多种超参数调优的方法:

    下面以Hyperopt为例说明贝叶斯优化的具体应用。

    对于一个优化问题我们可以分为4个部分:

    优化的目标函数

    优化的参数空间

    超参数优化方程,建立代理函数(surrogate function),并用其决定下一次尝试的参数组合

    Trials: 记录每次尝试的loss、参数及更多额外信息(可DIY),可以记录整个迭代的过程,用于回测。

    空间定义:

    from hyperopt import STATUS_OK, fmin, tpe, Trials, hp

    import xgboost as xgb

    import logging

    from timeit import default_timer as timer

    import os

    from functools import partial

    import pandas as pd

    import numpy as np

    MAX_EVALS = 100 # 迭代次数

    NFOLDS = 5 # K-FOLD

    FOLDS = None # 自定义的FOLDS,优先级高于NFOLDS

    BASE_DIR = os.path.dirname(os.path.abspath(__file__))

    XGB_SPACE = {

    ‘booster‘: ‘gbtree‘,

    ‘random_state‘: 2019,

    ‘eval_metric‘: ‘rmse‘,

    ‘n_jobs‘: -1,

    ‘learning_rate‘: 0.05,

    ‘subsample‘: hp.uniform(‘subsample‘, 0.1, 1.0),

    ‘colsample_bytree‘: hp.uniform(‘colsample_bytree‘, 0.1, 1.0),

    ‘max_depth‘: hp.quniform(‘max_depth‘, 5, 30, 1),

    ‘gamma‘: hp.uniform(‘gamma‘, 0.0, 2.0),

    ‘min_child_weight‘: hp.uniform(‘min_child_weight‘, 0.0, 5.0),

    ‘reg_alpha‘: hp.uniform(‘reg_alpha‘, 0.0, 3.0),

    ‘reg_lambda‘: hp.uniform(‘reg_lambda‘, 0.0, 3.0)

    }

    定义优化的目标函数:

    def objective_base(params,

    train_set,

    folds=None,

    nfold=5,

    writetoFile=True):

    """

    Objective function for Gradient Boosting Machine Hyperparameter Optimization

    Args:

    folds: This argument has highest priority over other data split arguments.

    Return:

    """

    # Keep track of evals

    global _ITERATION

    _ITERATION += 1

    # Make sure parameters that need to be integers are integers

    for parameter_name in [

    ‘num_leaves‘, ‘max_depth‘, ‘bagging_freq‘, ‘min_data_in_leaf‘,

    ‘min_samples_split‘, ‘min_samples_leaf‘

    ]:

    if parameter_name in params:

    params[parameter_name] = int(params[parameter_name])

    start = timer()

    logging.info(f"{_ITERATION} ITERATION")

    logging.info(f"params:\n{params}")

    cv_dict = xgb.cv(params,

    train_set,

    num_boost_round=5000,

    nfold=nfold,

    stratified=False,

    folds=folds,

    early_stopping_rounds=100,

    as_pandas=False,

    verbose_eval=10,

    seed=0,

    shuffle=False)

    # Extract the min rmse, Loss must be minimized

    loss = np.min(cv_dict[‘test-rmse-mean‘])

    # Boosting rounds that returned the lowest cv rmse

    n_estimators = int(np.argmin(cv_dict[‘test-rmse-mean‘])+1)

    run_time = timer() - start

    # Write to the csv file (‘a‘ means append)

    if writetoFile:

    random_datetime = str(int(time.time()))

    hyper_base_path = os.path.join(BASE_DIR, ‘hyperopt_output‘)

    trial_file = os.path.join(hyper_base_path, ‘trials.csv‘)

    trial_file_rename = os.path.join(hyper_base_path,

    ‘trials_%s.csv‘ % random_datetime)

    if not os.path.exists(hyper_base_path):

    os.makedirs(hyper_base_path)

    print(

    "No trial file directory exists, will be created..."

    )

    if os.path.exists(trial_file) and _ITERATION == 1:

    print("Trial file exists, will be renamed...")

    os.rename(trial_file, trial_file_rename)

    assert os.path.exists(

    trial_file

    ) == False, "Trial file still exists, rename failed..."

    # File to save first results

    of_connection = open(trial_file, ‘w‘)

    writer = csv.writer(of_connection)

    # Write the headers to the file

    writer.writerow(

    [‘loss‘, ‘params‘, ‘iteration‘, ‘estimators‘, ‘train_time‘])

    of_connection.close()

    of_connection = open(trial_file, ‘a‘)

    writer = csv.writer(of_connection)

    writer.writerow([loss, params, _ITERATION, n_estimators, run_time])

    # Dictionary with information for evaluation

    return {

    ‘loss‘: loss,

    ‘params‘: params,

    ‘iteration‘: _ITERATION,

    ‘estimators‘: n_estimators,

    ‘train_time‘: run_time,

    ‘status‘: STATUS_OK

    }

    定义前处理和后处理模块:

    def build_train_set(X_train, y_train):

    isX_df = isinstance(X_train, pd.DataFrame)

    isY_sr = isinstance(y_train, pd.Series)

    isY_df = isinstance(y_train, pd.DataFrame)

    if isY_df:

    raise TypeError(

    f"y_train is df, with the shape {y_train.shape}, which is not supportable now."

    )

    if isX_df ^ isY_sr:

    raise TypeError(f"X_train and y_train have different types!")

    if isX_df:

    train_set = xgb.DMatrix(X_train.values, y_train.values)

    else:

    train_set = xgb.DMatrix(X_train, y_train)

    return train_set

    def post_hyperopt(bayes_trials, train_set, folds=None, nfold=5):

    # get best params

    bayes_results = pd.DataFrame(bayes_trials.results)

    bayes_results = bayes_results.sort_values(by=‘loss‘)

    bayes_results.reset_index(drop=True, inplace=True)

    best_params = bayes_results.loc[0, ‘params‘]

    # get best loss and trees

    best_params[‘learning_rate‘] = 0.01

    # Perform n_folds cross validation

    cv_dict = xgb.cv(best_params,

    train_set,

    num_boost_round=5000,

    folds=folds,

    nfold=nfold,

    stratified=False,

    shuffle=False,

    early_stopping_rounds=100,

    as_pandas=False,

    verbose_eval=10,

    seed=2019)

    # Extract the min rmse, Loss must be minimized

    loss = np.min(cv_dict[‘test-rmse-mean‘])

    # Boosting rounds that returned the lowest cv rmse

    n_estimators = int(np.argmin(cv_dict[‘test-rmse-mean‘]) + 1)

    best_params[‘n_estimators‘] = n_estimators

    logging.info(f"best loss: {loss}, best n_estimators: {n_estimators}")

    logging.info(f"best params: {best_params}")

    return best_params, loss

    定义主函数:

    def main_tuning_with_bo(X_train,

    y_train,

    max_evals=MAX_EVALS,

    folds=FOLDS,

    nfold=NFOLD):

    # Keep track of results

    bayes_trials = Trials()

    # Global variable

    global _ITERATION

    _ITERATION = 0

    TRAIN_SET = build_train_set(X_train, y_train)

    SPACE = XGB_SPACE

    func_objective = partial(objective_base,

    train_set=TRAIN_SET,

    folds=folds,

    nfold=nfold,

    writetoFile=True)

    # Run optimization

    best = fmin(fn=func_objective,

    space=SPACE,

    algo=tpe.suggest,

    max_evals=max_evals,

    trials=bayes_trials,

    rstate=np.random.RandomState(2019))

    best_params, loss = post_hyperopt(bayes_trials,

    train_set=TRAIN_SET,

    folds=folds,

    nfold=nfold)

    return best_params, loss

    Reference

    原文:https://www.cnblogs.com/cx2016/p/12899500.html

    展开全文
    weixin_27006759 2021-01-14 15:23:10
  • 这里主要个方面对项目进行优化,分别是内存,资源,图形和GPU,编程和代码框架,项目中这种资源组件的配置。 资源 资源管线可以大幅影响应用程序的性能。 正确导入纹理 纹理会占用大部分内存,因此,导入...

    性能优化是游戏项目开发过程中一个永恒的话题。项目的性能优化主要围绕CPU、GPU和内存三大方面进行。但是如此的总结我感觉太繁杂不能成系统,例如:影响内存的主要三个部分1.资源内存占用;2.引擎模块自身内存占用;3.托管堆内存占用。你得资源和代码质量都会影响内存。这里主要从五个方面对项目进行优化,分别是资源内存图形和GPU编程和代码框架项目中各种资源组件的配置

    目录

    资源内存

    正确导入纹理

    调整网格导入设置

    检查多边形面数

    图形和GPU

    批处理执行绘制调用

    灯光

    相机

    编程和代码框架

    Update

    字符串

    不要使用LINQ

    注意装箱过程

    确保外部代码库不产生垃圾

    小心循环代码

    仅在需要时运行代码

    使用Array数组代替List列表

    使用Float运算替代Vector运算

    Camera.main

    使用LocalPosition替代Position

    对象池

    协程

    AssetBundle的卸载

    避免在运行时添加组件

    删除调试日志语句

    使用ScriptableObject

    使用NonAlloc函数

    重构代码来减小GC的影响

    项目中各种资源组件的配置

    UI

    音频

    动画

    物理

     总结


    资源内存

    资源管线可以大幅影响应用程序的性能。在一个较为复杂的大中型项目中,资源的内存占用往往占据了总体内存的70%以上。

    正确导入纹理

    纹理会占用大部分内存,因此,导入设置非常重要。通常,请遵循以下指导原则 :

    • 减小 Max Size :使用能生成视觉上可接受的结果的最低设置。这种非破坏性方式,可以快速降低纹理内存。

    • 使用 2 的幂 (POT) :Unity 要求移动端纹理压缩格式 (PVRCT 或 ETC)采用 POT 纹理尺寸。

    • 制作纹理图集 :将多个纹理放置到单个纹理中,可以减少绘制调用和加快渲染速度。使用 或Unity 精灵图集第三方Texture Packer可以制作纹理图集。

    • 关闭 Read/Write Enabled 选项 :如果启用,此选项在 CPU 和 GPU 可寻址内存中都会创建副本,纹理会占用双倍内存。大多数情况下,应保持此选项为禁用状态。如果要在运行时生成纹理,请通过 Texture2D.Apply 强制执行,并且传入设置为 true 的 makeNoLongerReadable。

    • 禁用不必要的 Mip Map :对于在屏幕上大小保持不变的纹理(如 2D 精灵和 UI 图形),Mip Map 不是必需的,对于与摄像机的距离会变化的 3D 模型,请保留 Mip Map为启用状态。

    • 压缩纹理。

     

    尽可能减少过度绘制和Alpha 混合

    避免绘制不必要的透明或半透明图像。这种方式导致的过度绘制和 Alpha 混合会极大影响移动平台。

    调整网格导入设置

    与纹理很像,如果导入时不小心,网格可能占用过多内存。要尽可能减少网格占用的内存,请执行以下操作 :

    • 压缩网格 :高性能压缩可以减少占用磁盘空间(但不会影响运行时的内存)。请注意,网格量化可能造成不准确,因此应试验不同的压缩级别,从而找到适合模型的压缩级别。

    • 禁用 Read/Write :如果启用此选项,内存中会有重复网格,网格的一个副本在系统内存中,另一个在 GPU 内存中。大多数情况下,应将其禁用(在 Unity 2019.2 以及更早版本中,此选项默认为选中状态)。

    • 禁用骨骼和 BlendShape :如果网格不需要骨架或 BlendShape 动画,请尽可能禁用这些选项。

    • 尽可能禁用法线和切线 :如果确信网格的材质不需要法线或切线,请取消选中这些选项,以节省更多内存。

     

    检查多边形面数

    分辨率越高的模型,需要的内存使用量越大,并且可能占用更长的 GPU 时间。

    使用细节级别 (lOD)

    随着对象移向远处,细节级别可以将它们切换为使用更简单的网格,以及更简单的材质和着色器,从而帮助提高 GPU 性能。

    使用遮挡剔除来移除隐藏的对象

    隐藏在其他对象之后的对象仍然可能渲染和使用资源。使用遮挡剔除可以将它们丢弃。摄像机之外的视锥体剔除 (frustum culling) 是自动执行的,遮挡剔除 (occlusion culling) 是则要经过烘焙过程。只需将对象标记为静态遮挡物或被遮挡物,然后通过 Window > Rendering > Occlusion Culling 对话框进行烘焙。尽管不是所有场景都适合,剔除在很多情况下都能改善性能。


    图形和GPU

    每一帧,Unity 都需要确定必须渲染哪些对象,然后创建绘制调用。绘制调用是调用图形 API 来绘制对象(如三角形),而批处理是要一起执行的一组绘制调用。

    批处理执行绘制调用

    将要绘制的对象组合为批次,可以尽可能减少在批次中绘制每个对象所需的状态更改。这种方式通过减少渲染对象的 CPU 开销,可以改善性能。Unity 可以使用以下几种方法将多个对象组合为较少的批次 :

    • 动态批处理 :对于小网格,Unity 在 CPU 上分组和转换顶点,然后一次性绘制它们。注意 :只在有足够低复杂度网格(少于 900 个顶点属性和不超过 300 个顶点)时使用这一方法。动态批处理程序不会对更大的网格进行批处理,如果启用会浪费 CPU 时间在每一帧都去查找要批处理的小网格。

    • 静态批处理 :对于不移动的几何体,Unity 可以减少所有共享相同材质的网格的绘制调用。它比动态批处理更有效,但使用更多内存。

    • GPU 实例化 :如果有大量相同的对象,这种方法通过图像硬件对它们进行更有效地批处理​

    • SRP 批处理 :在 Advanced 下面的 Universal Render Pipeline Asset 中启用 。这样可以SRP Batcher大幅提高 CPU 渲染速度,具体取决于场景。

    灯光

    避免使用过多动态光线

    避免向移动端应用程序添加过多动态光线。考虑采用其他方式,如对动态网格使用自定义着色器效果和光照探针,以及对静态网格使用烘焙光照。

    禁用阴影

    阴影投射可按 MeshRenderer 和光线禁用。尽可能禁用阴影可以减少绘制调用。

    您也可以通过向简单网格应用模糊纹理或在角色下面应用四边形来创建伪阴影。另外,可以使用自定义着色器创建模糊阴影。

    将光照烘焙到光照贴图中

    烘焙阴影和光照的渲染不会影响运行时性能。

    光照探针

    光照探针存储场景中的空白空间的烘焙光照信息并且提供高质量的光照(直接和间接)。它们使用球谐函数,这种函数的计算速度比动态光照快很多。

    相机

    限制摄像机的使用

    每个摄像机都会产生开销,无论它是否在做有意义的工作。只在有必要渲染时才使用摄像机组件。在低端移动平台,每个摄像机最多可以使用 1 ms CPU 时间。

    限制后期处理效果

    全屏幕后期处理效果(如发光)会极大降低性能。请在游戏的美术设计中谨慎使用这些效果。

    限制使用一些相机特效。


    编程和代码框架

    因为我是程序出身就仔细说一下代码的问题吧。

    每个 Unity 脚本都将按预定顺序运行多个事件函数。您应该了解 Awake、Start、Update 及其他创建脚本生命周期的函数之间的区别。

    Update

    尽可能减少每帧运行的代码。

    考虑代码是否必须每一帧都运行。将不必要的逻辑移出 Update、 LateUpdate 和 FixedUpdate。可在这些事件函数中方便地放置必须每帧更新的代码,但应提取出任何不需要以这种频率更新的逻辑。尽可能只在情况发生改变时才执行逻辑。

    如果确实 需要使用 Update,可以考虑每 n 帧运行一次代码。这是一种应用时间切片 (将繁重的工作负载分布到多个帧的常用技术)的方法,逻辑层和表现层分离,分层限帧和动态负载均衡。

        private int interval = 3;
        void Update()
        {
            if (Time.frameCount % interval == 0)
                Function();
        }
    
        void Function()
        {
    
        }

    不在Update方法中创建新对象

    理想情况下,开发者在Update、FixedUpdate或LateUpdate方法中不应该使用New关键字,而是应该使用已有对象。

    一次创建,多次重用

    这条规则的意思是:要在Start方法和Awake方法中分配所有内容。这条规则和第一条类似,其实只是从Update方法移除new关键字的另一种方式。

    开发者应该从Update方法移除有以下行为的代码:

    • 创建新实例

    • 寻找任意游戏对象

    然后,将这些代码移动到Start方法或Awake方法中。

    //未优化的代码
    
    private List<GameObject> objectsList;void Update()
    {
        objectsList = new List<GameObject>();
        objectsList.Add(......)
    }
    
    
    
    //优化后的代码
    
    private List<GameObject> objectsList;void Start()
    {
        objectsList = new List<GameObject>();
    }
    
    
    
    void Update()
    {
        objectsList.Clear();
        objectsList.Add(......)
    }

    GameObject.Find、GameObject.GetComponent 和 Camera.main( 在 2020.2之前的版本中)可能开销较大,应避免在 Update 方法中调用它们。而应在 Start 中调用它们,并且缓存相应结果。

    //未优化的代码
    
    void Update()
    {
        var levelObstacles = FindObjectsOfType<Obstacle>();
        foreach(var obstacle in levelObstacles) { ....... }
    }
    
    
    
    //优化后的代码
    
    private Object[] levelObstacles;
    
    
    
    void Start()
    {
        levelObstacles = FindObjectsOfType<Obstacle>();
    }
    
    
    
    void Update()
    {
        foreach(var obstacle in levelObstacles) { ....... }
    }

    尝试避免在Update方法中使用访问器,只在Start方法中调用一次访问器,并缓存返回的数值。

    //未优化的代码
    
    void Update()
    {
    
        //分配包含所有touches的新数组
        Input.touches[0];
    }
    
    
    
    //优化后的代码
    
    void Update()
    {
        Input.GetTouch(0);
    }
    
    
    
    //未优化的代码
    
    void Update()
    {
    
        //返回新的字符串(垃圾),然后对比2个字符串
        gameObject.Tag == "MyTag";
    }
    
    
    
    //优化后的代码
    
    void Update()
    {
        gameObject.CompareTag("MyTag");
    }

    避免空Unity 事件

    即使是空的 MonoBehaviour 也需要资源,因此应删除空的 Update 或 LateUpdate 方法。

    字符串

    使用哈希值而不是字符串参数

    Unity 不使用字符串名称对 Animator、Material 和 Shader 属性进行内部寻址。为了加快速度,所有属性名称都经过哈希处理为属性 ID,实际上是这些 ID 用于寻址属性。

    每当在 Animator、Material 或 Shader 上使用 Set 或 Get 方法时,请使用整数值方法而非字符串值方法。字符串方法只执行字符串哈希处理,然后将经过哈希处理的 ID 转发给整数值方法。

    避免字符串连接

    在涉及到垃圾分配的时候,字符串要特别注意。即使是基本的字符串操作,也可能产生大量垃圾。这是为什么呢?

    因为字符串是无法改变的数组。这意味着,如果要把两个字符串连接起来,我们会创建新数组,而旧数组会成为垃圾。所以我们可以使用StringBuilder避免或最小化这类垃圾分配。

    //未优化的代码
    
    void Start()
    {
        text = GetComponent<Text>();
    }
    
    
    
    void Update()
    {
        text.text = "Player " + name + " has score " + score.toString();
    }
    
    
    
    //优化后的代码
    
    void Start()
    {
        text = GetComponent<Text>();
        builder = new StringBuilder(50);
    }
    
    
    
    void Update()
    {
    
        //StringBuilder为所有类型重载了Append方法
        builder.Length = 0;
        builder.Append("Player ");
        builder.Append(name);
        builder.Append(" has score ");
        builder.Append(score);
        text.text = builder.ToString();
    
    }

    不要使用LINQ

    尽可能不要使用LINQ。也就是说,不要在任何经常执行的代码中使用LINQ。

    虽然使用LINQ可以使代码更容易阅读,但在很多情况下,这类代码的性能和内存分配都非常糟糕。

    注意装箱过程

    装箱过程会生成垃圾。什么是装箱过程呢?

    最常见的装箱过程是将数值类型,例如int,float,bool等传递到需要Object类型参数的函数时,所发生的过程。

    确保外部代码库不产生垃圾

    如果发现部分垃圾由Asset Store资源商店下载的代码产生,我们有多个解决方法。但在我们进行逆向工程并调试前,请再次查看Asset Store资源商店的相应页面,代码库是否有进行更新。

    在我们的项目中,我们使用的所有资源一直由资源的开发者进行维护,他们一直在进行性能更新,从而解决了我们的所有问题。

    所以,一定要让项目使用的依赖保持更新。如果遇到没有维护的代码库,建议放弃这类代码库。

    小心循环代码

    同Update一样。

    仅在需要时运行代码

    建议通过使用C#事件实现观察者设计模式。

    使用Array数组代替List列表

    在代码中,我们发现大多数列表有固定的长度,或是可以计算出最大成员数量。因此我们使用数组重新实现了这些列表,在特定情况下,可以在迭代数据的时候得到原先2倍的速度。

    在某些情况下,我们无法避免使用列表或其它复杂的数据结构。常见的情况是:需要经常添加或移除元素的时候,使用列表的效果更好的时候。通常来说,我们会对固定大小的列表使用数组。

    在堆内存上进行链表的分配的时候,如果该链表需要多次反复的分配,我们可以采用链表的clear函数来清空链表从而替代反复多次的创建分配链表。

    //优化前
    void Update()
    {
        List myList = new List();
        PopulateList(myList);       
    }
    //优化后
    private List myList = new List();
    void Update()
    {
        myList.Clear();
        PopulateList(myList);
    }

    使用Float运算替代Vector运算

    Float运算和Vector运算的区别不是很明显,除非像我们一样进行上千次运算,因此对我们来说,这项改动的性能提升效果非常明显。

    Camera.main

    使用Camera.main很简单,但是这种操作的性能非常糟糕。这是因为在每个Camera.main调用背后,Unity其实会执行FindGameObjectsWithTag()来获取结果,因此频繁调用Camera.main并不好。

    最好的解决方法是在Start或Awake方法中缓存Camera.main的引用。

    使用LocalPosition替代Position

    在代码允许的位置,为获取函数(Getter)和设置函数(Setter)使用Transform.LocalPosition替代Transform.Position。

    这样的原因是:每次Transform.Position调用的背后,都会有更多操作要执行,包括在调用获取函数时计算全局位置,或是在调用设置函数时从全局位置计算出本地位置。

    在项目中,我们发现在出现Transform.Position的几乎所有情况中都可以用LocalPosition替代Transform.Position,无需在代码中做其它改动。

    对象池

    对象池用于减少内存开销,其原理就是把可能用到到的对象,先存在一个地方(池),要用的时候就调出来,不用就放回去。而不是要用的时候创建,不用的时候销毁。

    协程

    调用 StartCoroutine()会产生少量的内存垃圾,因为unity会生成实体来管理协程。所以在游戏的关键时刻应该限制该函数的调用。基于此,任何在游戏关键时刻调用的协程都需要特别的注意,特别是包含延迟回调的协程,另外还要注意不要同时开启多个携程,必要的时候可以手动关闭协程StopCoroutine();

    AssetBundle的卸载

    不用的资源记得卸载。

    避免在运行时添加组件

    在运行时调用 AddComponent 需要一些开销。每当在运行时添加组件时,Unity 都必须检查是否有重复项或是否需要其他组件。

    删除调试日志语句

    日志语句(尤其是在 Update、LateUpdate 或 FixedUpdate 中)可能会降低性能。在进行构建之前,请禁用日志语句。

    使用ScriptableObject

    在 ScriptableObject 中而不是 MonoBehaviour 中存储不变的值或设置。ScriptableObject 这种资源只需设置一次就可以在项目中一直使用。它不能直接附加到游戏对象。

    在 ScriptableObject 中创建字段来存储值或设置,然后在 Monobehaviour 中引用该 ScriptableObject。

    使用 ScriptableObject 的这些字段可以防止每次使用该 Monobehaviour 实例化对象时出现不必要的数据重复。

    使用NonAlloc函数

    对于特定Unity函数,我们可以找到不分配任何内存的替代函数。在我们的项目中,这些函数都和物理功能有关。我们在碰撞检测使用的函数如下。

    Physics2D. CircleCast();

    对于该函数,我们可以找到不分配任何内存的版本。

    Physics2D. CircleCastNonAlloc();

    许多其它函数都有类似的替代函数,因此请记得查看文档,了解函数是否有相应的NonAlloc版本。

    重构代码来减小GC的影响

    即使我们减小了代码在堆内存上的分配操作,代码也会增加GC的工作量。最常见的增加GC工作量的方式是让其检查它不必检查的对象。struct是值类型的变量,但是如果struct中包含有引用类型的变量,那么GC就必须检测整个struct。如果这样的操作很多,那么GC的工作量就大大增加。在下面的例子中struct包含一个string,那么整个struct都必须在GC中被检查:

    //优化前
    public struct ItemData
    {
        public string name;
        public int cost;
        public Vector3 position;
    }
    private ItemData[] itemData;
    //优化后
    private string[] itemNames;
    private int[] itemCosts;
    private Vector3[] itemPositions;

    如果我们知道堆内存在被分配后并没有被使用,我们希望可以主动地调用GC操作,或者在GC操作并不影响游戏体验的时候(例如场景切换的时候),我们可以主动的调用GC操作:

    System.GC.Collect()

    总之代码的优化还有很多,平时要多注意这些细节养成良好的书写代码的习惯。


    项目中各种资源组件的配置

    这一块的内容主要是开发者对Unity引擎的熟悉程度,如果你工作时间长经历的项目多自然会知道这些组件的优化点。

    UI

    UGUI)常常是性能问题的来源。Canvas 组件生成和更新 UI 组件的网格并向 GPU 发出绘制调用。它的运行开销很大,因此,在使用 UGUI 时,请注意以下因素。

    隐藏不可见的UI 元素

    可能有些 UI 元素(如仅当角色收到伤害时才出现的生命值血条)只偶尔在游戏中出现。如果不可见的 UI 元素是活动的,它仍然可能使用绘制调用。显式禁用所有不可见的 UI 组件,在需要时再重新启用。

    如果只需要关闭画布的可见性,请禁用 Canvas 组件而不是游戏对象。这样就不必重新构建网格和顶点。

    限制GraphicRaycaster 和禁用Raycast Target

    输入事件(如屏上触摸或单击)需要 GraphicRaycaster 组件。它只是循环处理屏幕上的每个输入点,检查它是否在 UI 的 RectTransform 之内。

    从层级视图的顶层画布中移除默认的 GraphicRaycaster。只向需要交互的各元素(按钮、滚动矩形等)添加 GraphicRaycaster。

     另外,在所有不需要 Raycast Target 的 UI 文本和图像上将其禁用。如果是包含很多元素的复杂 UI,所有这些小更改都可以减少不必要的计算。

     避免使用布局组

    布局组的更新很低效,应少量使用。如果内容是动态的,应完全避免不用,而是使用锚点进行比例布局。或者,创建自定义代码,在Layout Group 组件设置 UI 之后,将该组件禁用。

    如果动态元素确实需要使用布局组(水平、垂直、网格),应避免嵌套它们,从而改善性能。

    使用全屏UI 时,隐藏其他全部内容

    如果暂停屏幕或者启动屏幕遮住场景中的其他全部内容,则禁用摄像机对 3D 场景的渲染。同样,禁用隐藏在顶层画布之后的所有背景画布元素。

    由于不需要以 60 fps 的帧率进行更新,可以考虑在全屏 UI 过程中降低 Application.targetFrameRate。

    合并图集

    减少DrawCall,多张图片需要多次DrawCall,合并成一张大图只需要调用一次DrawCall

    减少对内存的占用。

    音频

     尽管音频通常不会造成性能瓶颈,还是可以进行优化以节省内存。

    尽量使用单声道声音剪辑

    如果要使用 3D 空间音频, 请以单声道 (single channel) 的形式创作声音剪辑,或者启用 Force To Mono 设置。在运行时定位使用的多声道声音会扁平化为单声道源,因此会增加 CPU 开销和浪费内存。

    尽可能使用原始未压缩WAV 文件作为源资源

    如果使用任何压缩格式(如 MP3 或 Vorbis),Unity 会将其解压并在构建时重新压缩。这样会导致两个有损通道,从而降低最终质量。

    压缩剪辑并降低压缩比特率

    通过压缩减小剪辑的大小和内存使用量 :

    • 对大多数声音使用 Vorbis(或者对不循环的声音使用 MP3)。

    • 对常用的短声音使用 ADPCM(如脚步声、枪声)。相比于未压缩的 PCM,这样可以减小文件大小,在播放时又可以很快解码。

    移动设备上的音效最高为 22,050 Hz。使用较低设置通常对最终质量影响很小,当然,请使用您自己的耳朵来判断

    ​。

     选择正确的加载类型

    每个剪辑大小的设置都不同。

    • 小剪辑 (< 200 kb) 应采用 Decompress on Load。将声音解压缩为原始 16 位 PCM 音频数据,会导致 CPU 开销和内存占用,因此,这仅适用于短声音。

    • 中等剪辑 (>= 200 kb) 应保持为 Compressed in Memory。

    • 大文件(背景音乐)应设置为 Streaming。否则,整个资源会一次性加载到内存中。

    从内存中卸载静音的音频源 (AudioSources)

    实现静音按钮时,不要只是将音量设置为 0。可以销毁 AudioSource 组件,从而将其从内存中卸载,这样,播放器不需要过于频繁地切换开关。

    动画

    Unity的动画相当复杂。尽可能限制在移动设备上使用下面的设置。

    使用通用还是人形骨架

    默认情况下,Unity 通过通用骨架导入动画模型,但在动画化角色时,开发人员常常切换为人形骨架。

    人形骨架每一帧(即使未使用)都计算反向动力学和动画重定向,占用的 CPU 时间比等效的通用骨架多 30-50%。如果不需要这些特定人形骨架功能,请使用通用骨架。

    避免过多使用 Animator

    Animator 主要用于人形角色,但也常用于动画化单个值(如 UI 元素的 Alpha 通道)。避免过多使用 Animator,尤其是与 UI 元素结合使用。只要可能,对移动设备使用旧版 Animation 组件。

    考虑创建补间函数或者使用第三方库来实现简单动画(如 DOTween)。

    物理

    Unity 的内置物理系统 (Nvidia PhysX) 在移动设备上开销较大。下面的提示可以帮助您每秒减少更多帧。

    优化设置

    在 PlayerSettings 中,尽可能选中 Prebake Collision Meshes。

    请务必同时编辑 Physics 设置 (Project Settings > Physics)。尽可能简化 Layer Collision Matrix。

    禁用 Auto Sync Transforms 并启用 Reuse Collision Callbacks。

     简化碰撞体

    网格碰撞体开销较大。用简单的原始碰撞体或网格碰撞体代替更复杂的网格碰撞体来近似原始形状。

    使用物理方法移动刚体

    使用类方法(如 MovePosition 或 AddForce)来移动 Rigidbody 对象。直接转换其 Transform 组件可能导致重新计算物理世界,在复杂场景中,这样需要较大开销。

    在 FixedUpdate 中而不是 Update 中移动物理体。

    修改固定时间间隔

    Project Settings 中的默认 Fixed Timestep 是 0.02 (50 Hz)。根据目标帧率对此进行更改(例如,对 30 fps 设置为 0.03)。

    否则,如果帧率在运行时下降,也就是说 Unity 每帧都多次调用 FixedUpdate,可能会因物理内容过多而造成 CPU 性能问题。

    Maximum Allowed Timestep 对帧率下降时物理计算和 FixedUpdate 事件可以使用的时间进行限制。降低该值意味着在性能顿挫过程中,物理系统和动画会缓慢下来,但也会减小其对帧率的影响。

    总结

    性能优化是一个广泛的话题。理解移动硬件的运行方式及其限制。要找到符合您的设计要求的有效解决方案,您需要掌握 Unity 的类和组件、算法和数据结构,以及平台的性能分析工具。

    展开全文
    f402455894 2021-09-15 15:19:30
  • u010520724 2021-07-21 14:07:37
  • weixin_30803191 2021-07-05 00:12:12
  • Forever_wj 2021-10-23 16:20:59
  • weixin_32050033 2021-01-27 06:53:01
  • a934079371 2021-02-02 11:40:00
  • woniu211111 2021-09-25 00:45:09
  • cyl101816 2021-05-30 16:34:31
  • lfanyize 2021-12-11 18:38:28
  • qq_37744263 2021-06-20 11:09:57
  • wenyusuran 2020-12-24 08:46:10
  • weixin_33321637 2021-01-18 18:23:07
  • lgno2 2021-04-18 00:42:10
  • weixin_41261833 2021-04-19 17:27:05
  • shao_xuan_ 2021-11-23 22:25:44
  • datian1234 2021-05-25 14:59:06
  • Androidlushangderen 2021-08-08 11:58:23
  • weixin_43591980 2021-08-25 21:15:02
  • weixin_39737757 2020-12-19 16:09:36
  • weixin_43537385 2021-03-15 15:17:23
  • yujia_666 2021-03-31 22:28:18
  • weixin_38753262 2021-03-20 00:33:49
  • qq_34827674 2021-02-24 15:49:42
  • Z1591090 2021-07-15 18:03:55
  • jlq_diligence 2021-08-04 11:53:40
  • weixin_39849387 2021-03-14 15:20:05

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 308,898
精华内容 123,559
关键字:

产品优化应该从以下几个方面进行