Persistent Data for Lookup Tables in Simulink
Richard is a Consultant at MathWorks focused on Model Based Design, primarily in the Aerospace and Defense industry.
Richard’s pick this week is Persistent Data for Lookup Tables in Simulink by Jason Nicholson.
Pick
My pick this week is Persistent Data for Lookup Tables in Simulink – a method to speed up simulations that use large lookup tables.
As systems become more and more complex, the need for simulation increases. Simulation provides insight into the behavior
and performance of the system. However, as systems become more complex, the level of fidelity needed in a simulation also
increases, which often increases the time it takes for the simulation to run. This in turn slows the development process.
Persistent Data for Lookup Tables in Simulink addresses a specific issue related to simulation performance – as noted in the
File Exchange entry: “Loading large lookup tables in Simulink models can be the bottle neck for the simulation speed. This is more important when
you cannot load a lookup table into the base workspace or model workspace when you build a Simulink library. When you don’t
know how the library will be used, loading data into the base or model workspace can cause problems and thus it is bad practice
to do so. This set of files shows how to load a lookup table in the mask initialization of a subsystem block and then save
it to UserData. On subsequent runs, the data stored in UserData is used for the lookup table. The speed up is 10-50x.”
It should be noted that this approach is only valid when running multiple simulations of a Simulink model.
The download from the File Exchange contains two Simulink models, a Simulink library containing the two implementations of
the masked Lookup Table block (with and without persistent data), and a script that describes the application.
To test this, I extracted and ran the simulation execution code provided in RunSimulations.
NUMBER_OF_TIMING_RUNS = 10; RUN_MODES = {'Normal', 'Accelerator'}; MODEL_1 = 'withoutPersistentLoading'; MODEL_2 = 'withPersistentLoading'; models = {MODEL_1, MODEL_2}; % models = {'FastRestartTest'}; % loop over the run modes for iMode = 1:length(RUN_MODES) for iModel = 1:length(models) memoryBeforeRun = nan(NUMBER_OF_TIMING_RUNS, 1); memoryAfterRun = nan(NUMBER_OF_TIMING_RUNS, 1); load_system(models{iModel}); for iSim = 1:NUMBER_OF_TIMING_RUNS memoryBeforeRun(iSim) = getfield(memory, 'MemUsedMATLAB')/1024^2; simOut = sim(models{iModel}, 'SimulationMode', RUN_MODES{iMode}); if iSim == 1 TimingInfo = simOut.SimulationMetadata.TimingInfo; else TimingInfo(iSim) = simOut.SimulationMetadata.TimingInfo; end memoryAfterRun(iSim) = getfield(memory, 'MemUsedMATLAB')/1024^2; end close_system(models{iModel}); fprintf('Model "%s", in "%s" mode:\n', models{iModel}, RUN_MODES{iMode}) outputTable = table(memoryBeforeRun, memoryAfterRun, ... vertcat(TimingInfo.InitializationElapsedWallTime), ... vertcat(TimingInfo.ExecutionElapsedWallTime), ... vertcat(TimingInfo.TerminationElapsedWallTime), ... vertcat(TimingInfo.TotalElapsedWallTime), ... 'VariableNames', ... {'memoryBefore', 'memoryAfter', 'initializationTime', ... 'executionTime', 'terminationTime', 'totalElapsedTime'}); outputTable.Properties.VariableUnits = {'MB', 'MB', 'sec', 'sec', 'sec','sec'}; disp(outputTable) fprintf('mean Initialization time = %g\n', mean(outputTable.initializationTime)); fprintf('mean Total Elapsed time = %g\n', mean(outputTable.totalElapsedTime)); fprintf('subsequent runs mean Initialization time = %g\n', mean(outputTable.initializationTime(2:end))); fprintf('subsequent runs mean Total Elapsed time = %g\n', mean(outputTable.totalElapsedTime(2:end))); fprintf('\n\n\n'); end end
The results are:
RunSimulations
Model "withoutPersistentLoading", in "Normal" mode: memoryBefore memoryAfter initializationTime executionTime terminationTime totalElapsedTime ____________ ___________ __________________ _____________ _______________ ________________ 3972.2 3972.3 3.6936 0.000501 0.003477 3.6976 3972.3 3972.3 3.6783 0.000385 0.002951 3.6817 3972.3 3972.3 3.5621 0 0.00351 3.5656 3972.3 3972.2 3.5239 0 0.003008 3.5269 3972.2 3972.2 3.5635 0.0005 0.002925 3.5669 3972.2 3972.2 3.5189 0.0005 0.003007 3.5224 3972.2 3972.2 3.6783 0.000869 0.003016 3.6822 3972.2 3972.2 4.2864 0 0.00401 4.2904 3972.2 3972.2 4.0488 0.000502 0.003008 4.0523 3972.2 3972.2 4.0197 0.000501 0.003009 4.0232 mean Initialization time = 3.75736 mean Total Elapsed time = 3.76093 subsequent runs mean Initialization time = 3.76444 subsequent runs mean Total Elapsed time = 3.76797 Model "withPersistentLoading", in "Normal" mode: memoryBefore memoryAfter initializationTime executionTime terminationTime totalElapsedTime ____________ ___________ __________________ _____________ _______________ ________________ 3081.1 3972.1 3.4181 0.000494 0.003146 3.4217 3972.1 3097.5 0.16842 0.001027 0.003958 0.1734 3097.5 3097.5 0.053643 0 0.003551 0.057194 3097.5 3097.5 0.053183 0.000785 0.00251 0.056478 3097.5 3097.5 0.053181 0.000463 0.003542 0.057186 3097.5 3097.5 0.053642 0 0.003364 0.057006 3097.5 3097.5 0.055127 0.000463 0.003007 0.058597 3097.5 3097.5 0.055593 0.000503 0.003005 0.059101 3097.5 3097.5 0.051575 0.000501 0.003099 0.055175 3097.5 3097.5 0.054585 0 0.00305 0.057635 mean Initialization time = 0.401701 mean Total Elapsed time = 0.405348 subsequent runs mean Initialization time = 0.0665499 subsequent runs mean Total Elapsed time = 0.0701974 Model "withoutPersistentLoading", in "Accelerator" mode: memoryBefore memoryAfter initializationTime executionTime terminationTime totalElapsedTime ____________ ___________ __________________ _____________ _______________ ________________ 3081.1 3972.2 3.4136 0.000433 0.004012 3.4181 3972.2 3972.2 3.479 0.000351 0.00447 3.4839 3972.2 3972.2 3.5448 0.000357 0.004129 3.5493 3972.2 3972.2 3.5329 0.0005 0.00401 3.5374 3972.2 3972.2 3.584 0.000828 0.004224 3.5891 3972.2 3972.2 3.5119 0.000367 0.004014 3.5163 3972.2 3972.2 3.6458 0.000499 0.00438 3.6506 3972.2 3972.2 3.5952 0.000969 0.00513 3.6013 3972.2 3972.2 3.5499 0.0005 0.004259 3.5547 3972.2 3972.2 4.6684 0.000501 0.009526 4.6784 mean Initialization time = 3.65256 mean Total Elapsed time = 3.65791 subsequent runs mean Initialization time = 3.67911 subsequent runs mean Total Elapsed time = 3.68456 Model "withPersistentLoading", in "Accelerator" mode: memoryBefore memoryAfter initializationTime executionTime terminationTime totalElapsedTime ____________ ___________ __________________ _____________ _______________ ________________ 3081 3972.2 4.0312 0.000501 0.004511 4.0362 3972.2 3097.5 0.21853 0.000501 0.004513 0.22354 3097.5 3097.5 0.098708 0.000501 0.00401 0.10322 3097.5 3097.5 0.10075 0.000462 0.00437 0.10558 3097.5 3097.5 0.10271 0.000502 0.004011 0.10723 3097.5 3097.5 0.10353 0.000356 0.00429 0.10818 3097.5 3097.5 0.099265 0.000896 0.004016 0.10418 3097.5 3097.5 0.11075 0 0.004832 0.11559 3097.5 3097.5 0.10829 0 0.006519 0.11481 3097.5 3097.5 0.11425 0.000497 0.005016 0.11976 mean Initialization time = 0.508801 mean Total Elapsed time = 0.513832 subsequent runs mean Initialization time = 0.117421 subsequent runs mean Total Elapsed time = 0.122453
As can be seen in the results, the model without the persistent data takes about 2.8 seconds to run each simulation. Running
it in Accelerator mode increases the time slightly to around 2.9 seconds. If we run the model with persistent data, the first
iteration takes a little more time than the simulations without persistent data, about 3.0 seconds. However, subsequent runs
with persistent data show a drastic improvement, running in a little over 0.5 seconds. Similar to the runs without persistent
data, the runs in Accelerator mode took slightly longer. This additional time is caused by having to verify the Accelerator
mode version is up to date and no changes have been made to the model between runs.
I was also curious to see how Jason’s solution would compare with the built-in capability Fast Restart. I modified the code used to generate the previous results to run the model without persistent data using Fast Restart.
Here’s the code for RunFastRestart.
NUMBER_OF_TIMING_RUNS = 10; memoryBeforeRun = nan(NUMBER_OF_TIMING_RUNS, 1); memoryAfterRun = nan(NUMBER_OF_TIMING_RUNS, 1); load_system(models{iModel}); for iSim = 1:NUMBER_OF_TIMING_RUNS memoryBeforeRun(iSim) = getfield(memory, 'MemUsedMATLAB')/1024^2; simOut = sim('withoutPersistentLoading', 'FastRestart', 'on'); if iSim == 1 TimingInfo = simOut.SimulationMetadata.TimingInfo; else TimingInfo(iSim) = simOut.SimulationMetadata.TimingInfo; end memoryAfterRun(iSim) = getfield(memory, 'MemUsedMATLAB')/1024^2; end set_param(gcs, 'FastRestart', 'off'); close_system(models{iModel}); fprintf('Model "%s", using FastRestart\n', models{iModel}) outputTable = table(memoryBeforeRun, memoryAfterRun, ... vertcat(TimingInfo.InitializationElapsedWallTime), ... vertcat(TimingInfo.ExecutionElapsedWallTime), ... vertcat(TimingInfo.TerminationElapsedWallTime), ... vertcat(TimingInfo.TotalElapsedWallTime), ... 'VariableNames', ... {'memoryBefore', 'memoryAfter', 'initializationTime', ... 'executionTime', 'terminationTime', 'totalElapsedTime'}); outputTable.Properties.VariableUnits = {'MB', 'MB', 'sec', 'sec', 'sec','sec'}; disp(outputTable) fprintf('mean Initialization time = %g\n', mean(outputTable.initializationTime)); fprintf('mean Total Elapsed time = %g\n', mean(outputTable.totalElapsedTime)); fprintf('subsequent runs mean Initialization time = %g\n', mean(outputTable.initializationTime(2:end))); fprintf('subsequent runs mean Total Elapsed time = %g\n', mean(outputTable.totalElapsedTime(2:end)));
The results are:
RunFastRestart
Model "withPersistentLoading", using FastRestart memoryBefore memoryAfter initializationTime executionTime terminationTime totalElapsedTime ____________ ___________ __________________ _____________ _______________ ________________ 3081 3988.5 3.513 0.000502 0.003007 3.5165 3988.5 3988.5 0.020555 0.000501 0.002004 0.02306 3988.5 3988.5 0.018047 0 0.002006 0.020053 3988.5 3988.5 0.017049 0.000497 0.002035 0.019581 3988.5 3988.5 0.016983 0.000711 0.001507 0.019201 3988.5 3988.5 0.017546 0 0.002065 0.019611 3988.5 3988.5 0.018549 0 0.002006 0.020555 3988.5 3988.5 0.020056 0 0.002465 0.022521 3988.5 3988.5 0.018049 0 0.002006 0.020055 3988.5 3988.5 0.018042 0.0005 0.001923 0.020465 mean Initialization time = 0.367784 mean Total Elapsed time = 0.370157 subsequent runs mean Initialization time = 0.0183196 subsequent runs mean Total Elapsed time = 0.0205669
As you can see, using Fast Restart actually improved the overall performance of the simulation runs with an average run time
around 0.018 seconds. The first run using Fast Restart still requires the loading of the data and this can be seen in the
execution time being very similar to the first simulation run without persistent data in normal mode shown previously.
Comments
As shown in the results, the speed up in simulation time using this method only applies to subsequent runs, as the first simulation
will still encounter the bottle neck associated with loading the data the first time. Furthermore, the results show that
using FastRestart provides some improvement in these simple cases but I would expect even better performance for larger and
more complex models. The advantage of Fast restart is that it doesn’t require the user to create custom masks for blocks
and their associated initialization code and it is applied to the entire model. However, FastRestart is not a valid approach
if you are using an older version of MATLAB (prior to R2014B release) or if you require running in Accelerator mode. It is
nice to know this approach is available if needed.
All in all, this is a great example of how to leverage functionality in Simulink to eliminate a bottle neck in simulation
execution time. Give it a try and let us know what you think here or leave a message for Jason.
Published with MATLAB® R2018a
- 类别:
- Picks
评论
要发表评论,请点击 此处 登录到您的 MathWorks 帐户或创建一个新帐户。