Simulink仿真基础:性能优化相关知识

描述

0. 概述

加速 Simulink 仿真是个老生常谈的问题,尤其是系统比较复杂或规模较大的时候,大家都不希望时间耗费在等待上,也正因此,其实已经有很多答案散落在各种地方;考虑到老朽本人玩 Simulink 不多甚至可以说很少,所以这篇以搬砖为主,整理编撰的时候会尽量注明出处,也尽量对各种方法分门别类让大家可以一目了然。当然,本着严谨的技术态度,老朽还会尽量做一些验证,确保拿出来的东西都是能用得上的。(由于本文涉及的外部链接较多,我将一一列在文末,方便大家获取。)

首先,有必要澄清一下,这里的 Simulink 其实不只是 Simulink,而仿真耗时也不只是模型求解慢。

从模型本身来讲,它可能由以下一种或多种情形组合(甚至是全部):

1Simulink(数学)模型

2Simscape(物理)模型

3Stateflow(状态机)模型

4SimEvents(事件)模型

5MATLAB(脚本)模型

6其它第三方模型(以源代码或动态库等方式集成)

其中物理模型可能是 Driveline,Multibody,Electrical,Fluids 以及 Powertain 和 Vehicle Dynamics 中的一种或多种组合。

MATLAB 脚本可能只是一个简单的计算,一个传递函数或状态方程,也可能是机器学习或深度学习的推断,亦或是强化学习智能体。

本文重点讨论 Simulink 与 Simscape,也会提及其它的方面,对于复杂的情况,建议还是找 MathWorks 官方提供技术支持(版本在更新,技术在发展,过去没有改善的不表示现在不能改善)。

从仿真耗时来讲,它可能会涉及以下时间成本:

1模型编译/链接

2循环初始化

3循环迭代

4输出/日志(在循环迭代过程中)

5仿真终止(在循环迭代结束时)

列举完这些方方面面,大概就能有一个概念——仿真加速要针对具体的情况具体分析。

尽管老朽有理由相信来读这篇文章的大都是有经验的 Simulink 用户,但稳妥起见,我们还是从基础开始——其实,经验也常常教育我们,涉及到性能的问题,确实有不少情况都是基础没打好。

1. 基础:性能优化相关知识

基础部分的知识点来自 MATLAB产 品文档,着急直接上手优化仿真性能的观众,可以直接去看这个:优化性能 - MathWorks 官网文档[1]。但俗话讲得好,心急吃不到热豆腐,耐心点往下看,我相信会有更多收获。

在看这部分之前,无论你是 Simulink 老用户还是新用户,我都要墙裂建议你看一下帮助文档里仿真原理的相关章节,MathWorks已经提供部分中文化的pdf版本了,其中 Simulink 的工作原理一章已经有中文版了,给懒人们直接上链接吧:Simulink 用户指南 (仅已翻译的部分 R2021b)[2],翻到第99页,从这里开始,看完第三章的29页即可。

如果你的模型里还有 Simscape 或 Stateflow 或 SimEvent 等等,我还要建议你更进一步,把它们的 User's Guide 里有关原理的章节都看一下:

● Simulink 用户指南 (仅已翻译的部分 R2021b)[3],第3章 Simulink 的工作原理,共29页

● Simscape User's Guide:[4]第7章 Model Simulation,共69页

● Stateflow 用户指南 (仅已翻译的部分 R2021b):[5]第3章 Stateflow语义,共24页

● SimEvents User's Guide:[6]这个有点分散,没用到就先放放吧,讲真老朽也没看过

关于基础知识,大家看 MATLAB 帮助文档学习的话,老朽建议一定要看 PDF 版本的 User's Guide 也就是用户指南,相比去官网上看或者在帮助界面上看,PDF 版本的用户指南都要好看的多。

言归正传,回到加速 Simulink 仿真的问题,在 Simulink 用户指南(R2021b 中文版)的动态仿真部分,分别在第31章和第35章,给出了“如何提高仿真性能”以及“加速模型”两章较为全面的阐述,大家把这些看完看懂的话,老朽相信大家对于自己的仿真为什么会慢,以及怎样改进模型使仿真能快一些,就已经有谱了。

鉴于以上提到的方方面面,显然不合适把重点全抄下来,而老朽又想大家即使只看完本文也能做些工作,因此就挑重点半抄半写吧。

基础方面,我打算罗列6个要点,考虑到内容真的有点多,没法具体展开,要细节的话还是去看文档吧。

1.1 动态系统仿真的3个阶段

编译阶段

链接阶段

仿真环阶段

所谓3个阶段,就是从你按下绿色的运行按钮,到仿真跑出结果,Simulink 要干的三件事情。看名字就很好理解它们都是什么,为了让偷懒的也能看看,就把文档内容拷贝到本节后面吧。

大多数有关仿真提速的内容都是针对第三阶段的,即仿真的循环初始化和循环迭代两个部分,但作为开篇,我想重点先说说模型编译。经常有人在大型建模上耗费了大把时间等待模型编译,加快超大模型的编译,有几点知识和经验需要了解:

01模型的编译跟 C/C++ 文件的编译类似,其实是可以支持多 CPU 核(多进程)同时编译的,前提是你得遵循“基于组件的建模规范”,即在你的模型中合理使用子系统、链接子系统、模型引用和子系统引用。具体我们在1.3模型架构里讲。

02Simulink 支持增量式加载/编译,在满足前一条的情况下,可以通过共享 Simulink Cache 文件来降低编译耗时,具体参见:共享 Simulink 缓存文件以加快仿真速度[7]。

03如果不是采用普通模式,而是用加速或快速加速模式,则 Simulink 不仅会经历模型编译,还会调用 Simulink Coder 的代码生成功能(虽然不需要为此购买和安装 Simulink Coder),生成 C/C++ 代码并调用 C/C++ 编译器将其编译为可执行文件,这种情况的模型编译耗时更长,对架构做的差的超大模型而言,甚至会卡死。这一点在1.5仿真模式中还会再提到。

04通过 Simulink Cache文件(.slxc)来减少模型编译时间这一功能,到 R2019b 才健全(支持代码生成和 Stateflow),以此为例,谈性能真的不能离开 MATLAB 最新版(后面关于求解器、多处理器和 GPU 都会提到)。

· 模型编译

当系统的模型处于打开状态,并且您对模型进行仿真时,将进行仿真的第一个阶段。在 Simulink 编辑器中,点击运行。运行仿真会导致 Simulink 引擎调用模型编译器。模型编译器会将模型转换为可执行形式,这个过程称为编译。具体说就是,编译器会:

• 计算模型的模块参数表达式,以确定它们的值。

• 确定模型没有显式指定的信号属性,例如,名称、数据类型、数值类型和维度,并检查每个模块是否都接受连接到其输入的信号。

• 将源信号的属性传播到它所驱动的模块的输入,以便计算模块中先前未指定的属性。

• 执行模块简化优化。

• 通过将虚拟子系统替换为它们所包含的模块,将模型层次结构扁平化。

• 通过基于任务的排序确定模块执行顺序。

• 确定模型中您未显式指定其采样时间的所有模块的采样时间。

这些事件本质上与您更新模块图时发生的情况相同。差别在于 Simulink 软件作为模型仿真的一部分启动模型编译,编译会直接进入链接阶段,如“链接阶段”中所述。而显式模型更新是针对模型的独立操作。

编译模型或模型层次结构时,可以通过点击进度条旁边的取消按钮来取消模型编译。

· 链接阶段

在此阶段,Simulink 引擎为工作区域(信号、状态和运行时参数)分配执行模块图所需的内存。它还为用于存储每个模块的运行时信息的数据结构体分配和初始化内存。对于内置模块,模块的主要运行时数据结构体称为 SimBlock。它存储指向模块的输入和输出缓冲区以及状态和工作向量的指针。

方法执行列表

在链接阶段,Simulink 引擎还会创建方法执行列表。这些列表列出了调用模型的模块方法以计算其输出的最有效顺序。在模型编译阶段生成的模块执行顺序列表用于构造方法执行列表。

模块优先级

您可以向模块分配更新优先级。较高优先级模块的输出方法在较低优先级模块的输出方法之前执行。仅当这些优先级与其模块执行顺序一致时,才会遵循它们。

· 仿真环阶段

链接阶段完成后,仿真会进入仿真环阶段。在此阶段,Simulink 引擎使用该模型提供的信息,从仿真开始到完成的时间段内以固定间隔连续计算系统的状态和输出。计算状态和输出的连续时间点称为时间步。时间步之间的时间长度称为步长。步长取决于用来计算系统连续状态、系统基础采样时间以及系统的连续状态是否具有不连续性。

仿真环阶段有两个子阶段:循环初始化阶段和循环迭代阶段。初始化阶段在循环开始时出现一次。迭代阶段在从仿真开始到仿真停止的时间段内的每个时间步重复一次。在仿真开始时,模型将指定要仿真的系统的初始状态和输出。在每个时间步中,将计算系统的输入、状态和输出的新值,并且将更新该模型以反映计算的值。仿真结束时,该模型将反映系统的输入、状态和输出的最终值。Simulink 软件会提供数据显示和日志记录模块。您可以通过在模型中包含这些模块来显示和/或记录中间结果。

仿真环阶段的速度提升,除了建模本身的影响,在选择求解器和发挥硬件性能(多核、GPU、超算)方面是相辅相成的,这个也跟 MATLAB 的版本更新有着密切的联系,除了求解器会在1.4中提到,后面在第2、3、4里都是跟这两相关的。

1.2 建模方法

假设我们要仿真一个闭环的自动驾驶系统模型。

Simulink

在这里,用什么方法分别对传感(Sensors)、感知(Perception)、规划(Planning)、控制(Control)以及被控对象(Plant model, Passenger Vehicle)和环境/场景(Environment/Scenario)建模,以及如何连接各子系统,对整体上的仿真速度显然有很大的影响。

从场景建模来说,现在 MATLAB 支持 Unreal Engine,以及 RoadRunner 工具链,这一块已经可以单独跑了(单机多进程,或者干脆双机分开跑),你可以看作是一种联合仿真,在考虑仿真性能优化的时候基本可以忽略它们,堆硬件(GPU)是最直接靠谱的出路——对于多体动力学的仿真,你如果从 UG 或 Solidworks 等软件导入 3D 模型,在 Simulink 的仿真过程中展示 3D 动态效果,配置一块独立显卡也是非常有必要的。

被控对象建模则有不同的方法。过去我们往往在模型的保真度和计算性能上做取舍,或者结合更抽象高效的数学模型与查表来避免对机电液物理模型的求解并同时达到合适的仿真精度要求,但这对于要考虑整车众多性能(诸如电池充电状态、速度曲线、续航能力、热管理水平等)的系统级仿真而言,模型大了跑起来就很慢,因此借用深度学习和实验数据构建降阶的替代模型就成了一种可能的优雅的选择。

关于用深度学习来实现模型降阶,这里给一篇文章供大家参考:

至于数学模型的构建,更多还是要看我们建模的经验和水平,Simulink 用户指南第15章,建模最佳实践中给出的对串联 RLC 威廉希尔官方网站 建模求解的例子,就很好的阐释了什么是“最佳形式的数学模型”,虽然过于简单了点,但很有参考意义。

关于采用什么样的方法建模,归纳起来有以下几点供参考:

· 对于数学模型,尝试找到它的最佳形式

· 对于多域物理系统,建议优先考虑 Simscape。在必要的情况下(Simscape 不满足需求)或者对数学方法构建被控对象模型很熟悉的情况下可以用数学方式建模,但自己造轮子要求总归是比较高的。

使用 Simscape 将跨越机械、电气、液压和其他物理领域的系统建模为物理网络。Simscape 构造描述模型行为的 DAE。软件将这些方程与模型的其余部分集成,然后直接解算 DAE。Simulink 同时对不同物理领域中的组件变量求解,从而避免代数环问题。

· 对于大型物理网络,如果所有子系统都是建的详细模型,则很难让仿真跑得快,建议充分利用变体,对于分析目标影响不大的子系统,使用诸如查表的方法表达外特性或者抽象的物理模型替代(如用理想开关替代 IGBT,甚至用等效威廉希尔官方网站 替代开关从而进一步提高仿真效率),更进一步,还可以用数据驱动+深度学习的方法来建模。

· 以上基本都是宏观方向上的,细节在哪里?看这个:Simulink 用户指南 (仅已翻译的部分 R2021b)[8],第31章 动态系统仿真 -> 提高仿真性能和准确性 -> 提高性能的建模方法,内容较多,涵盖“加速初始化阶段”、“降低模型交互性”、“降低模型的复杂度”、“选择和配置求解器”、“保存仿真状态”各个方面,后面的章节只有求解器会重复,建议细看一下(4页文档)。

1.3 模型架构

一个好的模型,跟一份好的代码,有一个最重要的共同点就是架构都要好。相较于写代码不管架构只堆功能(有点难),在 Simulink 里画模型不看架构显然更容易过得下去,而且很多情况甚至过得还不错,但跟代码一样,一旦整体规模上去了,就成了灾难——好在 Simulink 模型重构还是要比代码来得简单些。

一个好的模型架构,不仅意味着易于维护、易于扩展,也意味着易于测试(确保单元测试覆盖率),更是有效提高仿真性能的基础。有关模型架构,可以参考 Simulink 用户指南 (仅已翻译的部分 R2021b)[9],第22章“大型建模”中的“基于组件的建模规范”和“比较模型组件的功能”,这里抄录性能相关的部分供参考。事实上,有关建模规范,尤其是在满足高可靠高完整性的要求方面,有更完备的内容,但这不在本文探讨范围内,有兴趣的可以去MathWorks 官网查询或者联系 MathWorks 销售以便跟他们的工程师直接交流。

组件建模要求考虑事项——“性能要求”

• 增量模型加载

• 编译工件重用

• 降低大型模型的内存使用量

• 消除人为代数环

Simulink

对于不太想了解这么多细节的人来说,我们简单归纳几点在下面,作为大家建模时在仿真性能方面的参考:

· 模型引用!模型引用!模型引用!重要的事情说4遍,多用模型引用(Model Reference)。模型引用对于仿真性能的四个方面均有重要影响

· 子系统引用是 R2019b 版本才出来的,子系统引用的输入输出端口支持 Simscape 物理连接

· 模型引用虽然出来很早,但其端口至今仍不支持 Simscape 物理连接(R2022a),因此如果想用模型引用对物理网络进行分割封装,需要将端口等效转换为 Simulink 信号(自行分解物理网络有难度,可以向 MathWorks 咨询)

· 子系统引用虽然不支持编译工件重用,但它支持增量方式加载;换句话说,使用引用子系统不能带来模型编译上的效率提升,但对维护超大物理网络还是有用的

Simulink

三种组件化技术

在模型架构的最后部分,要提一下利用多 CPU 核提速单一模型仿真的问题,换句话说,就是多线程协同仿真的问题,这个问题重点会在第二章的进阶部分讲,这里提一下,是因为它跟模型引用密切相关。

1.4 求解器的选择

求解器的选择是一个很经典又富有挑战的话题,说很经典,是因为求解器算法本身大都岁月悠久了,譬如龙格-库塔(Runge-Kutta)迭代求解非线性常微分方程的方法于 1900 年左右被提出,欧拉方法则更早;而挑战呢,似乎是 Simulink 不像一些特定领域的专业软件有默认的甚至固定的求解器(它们往往只需求解某一类问题),在 Simulink 上,你还得自己选求解器(当然,从 R2015b 开始你可以选 auto solver 让它自动给你选),可选项还挺多,常常不知道该选啥才能跑的快(还得结合模型的特性、精度要求与采样时间的要求等)。

另一个方面,因为有其它专业软件在仿真速度方面的比较,也常常有人(包括我自己)会质疑是不是 MATLAB 的求解器不行,而忽略了在建模方法上的差异,这就很容易引错方向。

考虑到求解器的这些个情况,我就不拷贝黏贴了细节了,去下面两个链接自己看吧:

• 比较求解器[10](参见 Simulink 用户指南 第3章 Simulink 简介 -> Simulink 工作原理 -> 比较求解器)

• 选择求解器[11](参见 Simulink 用户指南 第3章 Simulink 简介 -> Simulink 工作原理 -> 比较求解器)

Simulink

求解器的选择

当然,关于求解器,老朽也有几点额外说明:

· 从 R2015b 开始,你可以使用 auto solver(自动求解器),让 Simulink 自动选择一个求解器与步长进行仿真。自动求解器根据模型的动态特性,推荐一个固定步长或可变步长求解器,自动确定最大步长值。R2016a 开始自动求解器能计算模型刚度,对于刚性模型会选择 ode15s 求解。

· 从 R2019a 开始,支持 odeN solver,能快速求解有过零检测的系统。“Simulink 提供了一个变步长求解器,允许您使用变步长(无误差控制)积分来求解动态模型,同时保持过零点的精度”

Simulink

odeN (Nonadaptive)

• 从 R2018a 在 Simscape 中引入 Partitioning Solver[12] 后(限制较多不太好用),MathWorks Advisory Board(MAB)就在讲 Local solver,然后到 R2022a 才真正发布对 Local solver 的支持。通过 Local solver,可以为模型的不同部分(模型引用!)选择不同的求解器,从而来提高仿真速度。这个新特性允许我们为较慢的模型选择计算成本更低的求解器。请注意:模型引用模块上 Local Solver 选项默认是关着的,你得自己打开它。

Local Solver Basics[13]

Use local solver when referencing model[14]

• 从 R2022a 开始,使用固定步长的求解器也可以很好地应对过零检测了。这对时间步长偏大的情况很有用。给张图大家自己看看吧~~

Simulink

定步长求解器的过零检测

• 看到这里,是不是要说:你看,还是求解器的水平问题嘛~~呃,好吧,求解器确实重要,但更重要的是建模的方法,不然你就得自己造轮子实践了。

• 无论是 odeN(R2019a)、ode1be(R2020a),还是对 local solver(R2022a)的支持,都未必能解决你的问题,现实的约束常常让人头疼,但是很抱歉,关于求解器老朽也只能到这里了,而且具体选哪个,参数怎么设,还是得 case by case 来看,实在搞不定,还是找 MathWorks 直接问吧~~

1.5 仿真模式

前面几个部分写得真的是太费力了,这部分还是拷贝粘贴吧。以下内容出自 Simulink 用户指南第35章加速模型。

加速和快速加速模式使用 Simulink Coder 产品的部分内容创建可执行文件。加速和快速加速模式会替换 Simulink 仿真中常用的解释代码,从而缩短模型的运行时间。虽然加速模式会使用一些 Simulink Coder 代码生成技术,但您不需要安装 Simulink Coder 软件即可为您的模型加速。

· 普通模式

在普通模式下,MATLAB 技术计算环境是 Simulink 软件的基础环境。Simulink 控制仿真过程中使用的求解器和模型方法。模型方法包括模型输出的计算等内容。普通模式在一个进程中运行。

Simulink

普通模式

· 加速模式

默认情况下,加速模式采用即时 (JIT) 加速方式在内存中生成执行引擎,而不是生成 C 代码或 MEX 文件。您还可以将模型回退到经典加速模式,在这种模式下,Simulink 将生成代码并将代码链接到 C-MEX SFunction。在加速模式下,模型方法与 Simulink 软件相分离,它们将作为之后进行仿真时使用的加速目标代码的一部分。Simulink 会在重用加速目标代码之前检查代码是否为最新版本。有关详细信息,请参阅“Code Regeneration in Accelerated Models”。在加速模式下,有两种操作模式。即时加速模式 在此默认模式下,Simulink 在内存中只为顶级模型(而不为引用模型)生成执行引擎。因此,仿真过程中不需要使用 C 编译器。由于加速目标代码在内存中,因此只要模型处于打开状态,就可以重用这些代码。Simulink 还会序列化加速目标代码,因此当模型处于打开状态时,不需要重新构建模型。

Simulink

即时加速模式

经典加速模式

要使用生成 C 代码的经典加速模式对您的模型进行仿真,请运行以下命令:

set_param(0, 'GlobalUseClassicAccelMode', 'on');

在此模式下,Simulink 会生成代码并将代码链接到与 Simulink 软件进行通信的共享库。MATLAB 与Simulink 的目标代码执行过程相同。

Simulink

经典加速模式

· 快速加速模式

快速加速模式从您的模型中创建一个快速加速独立可执行文件。这个可执行文件包含求解器和模型方法,但位于 MATLAB 和 Simulink 的外部。它使用外部模式(请参阅“External Mode Communication”(Simulink Coder))与 Simulink 通信。MATLAB 和 Simulink 在一个进程中运行,如果有第二个处理内核可用,独立可执行文件将在该内核中运行。

Simulink

快速加速模式

关于如何选择仿真模式,大家看文档吧,中文的:选择仿真模式[15]。提炼几句话放下面供参考:

· 普通模式在调整模型和显示结果方面提供了最大的灵活性,但运行速度最慢。

· 加速模式在性能以及与模型的交互方面介于普通和快速加速模式之间。加速模式不支持大多数运行时诊断。

· 快速加速模式的运行速度最快,但此模式不支持调试器或探查器,而且仅适用于模型中的所有模块可生成 C/C++ 代码或编译为 MEX 文件的模型。

另外,快速加速模式支持仿真目标语言为C++也是最近版本才有的功能(似乎文档里甚至都没公开),这个主要解决了深度学习不支持C语言代码生成的问题~~旧版本的话,折中的办法是先生成 C++,再用 C 封装,编译成 dll 或 mex 后再集成进来,道路有点曲折,好在现在不需要这么干了。

1.6 性能问题分析

其实这节应该放到最前面,但考虑到如果不了解基础光靠工具分析也很难解决根本问题,所以放到这还是更合适些。

Simulink 里其实提供了两个分析性能的手段,但性能分析要结合前面的建模与求解器一起来做,给张图:

Simulink

加速仿真需要关注每个阶段

具体的分析收到要落实到两个工具上,分别是 Performance Advisor 和 Solver Profiler,具体这里就不描述了,大家看文档更靠谱(这两部分在 Simulink User's Guide 英文版pdf里才有,因为截至 R2022a 还没中文化):

· Improve Simulation Performance Using Performance Advisor[16](英文 Simulink User's Guide 第34章)

· Examine Model Dynamics Using Solver Profiler[17](英文 Simulink User's Guide 第35章)

这里,我们来举一个实战小栗子,尝试用 Performance Advisor 找到原因并修改模型使它跑的快起来(如果很熟悉 Simulink,就不用废那么大功夫了)。

源头是我这篇文章里提到的问题:老朽笔记:MATLAB 强化学习入门(1+)[18],把强化学习智能体替换成只有推理模型后,仿真变慢了 N 倍,当时没找到原因。

那么,我们打开模型,然后切换到“调试”菜单,点击“性能顾问”,打开性能顾问(performance advisor)的界面。

Simulink

点击“运行所选检查”,经过漫长等待,得到报告:

Simulink

所有问题项看下来,其它都改了,没有效果,剩下这项:检查驱动导数端口的离散信号!我们再打开两个模型,将信号的全部属性召唤出来(点击左边工具条上的双箭头图标,选“全部”即可),很显然,区别就在 Calculate Observation 模块的输出原本是采样时间为 0.025 的离散信号,变成了连续信号(FiM固定子步),这导致后面的 MATLAB function(evaluatePolicy)以及机器人物理模型的计算量急剧增加。

Simulink

我们要做的就是修改 Calculate Observation 模块中最右边一个模块 RateTransition 的采样时间,强制它为 0.025,如此就能得到跟原模型一样的仿真速度(和精度)了。

Simulink

Simulink

至于 Simulink 默认行为为何会变,还是等老朽问完专家再来探讨吧。

审核编辑:汤梓红

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分