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 帐户或创建一个新帐户。