Perform Instrumentation-Free Profiling Using Hardware Execution Tracers
In Create Execution-Time Profile for Generated Code, execution-time profiling uses instrumentation code, which:
Modifies the generated code.
Adds overheads to the execution-time metrics.
For processor-in-the-loop (PIL) simulations, you can automatically filter the overheads–see Remove Instrumentation Overheads from Execution Time Measurements. If you can set up your target hardware to generate a code execution trace, then you can generate execution-time metrics without instrumentation code. You can:
Implement and register a parser for your target hardware.
Use the parser to produce execution-time metrics that do not contain overheads.
View execution-time metrics by using the Code Profile Analyzer.
Workflow to Define and Use Parser
The format of the execution trace from target hardware profiling events is target specific. Your parser must convert the execution trace to a form that MATLAB® can process.
Use the coder.profile.trace
API to:
Abstract trace information from the source, for example, target hardware, a simulator, or an external debugger.
Provide execution-time information to MATLAB.
Use this workflow:
Implement a parser that inherits from
coder.profile.trace.Parser
and overrides these protected methods:processCustomFields
–– Processes additional information that is required to parse the trace. Registered information is passed to the method by using name-value arguments.parseTrace
–– Parses the file that contains the execution trace. While parsing the trace, the code notifies the logger, acoder.profile.trace.Logger
object, of profiling events by calling the methodsstartTask
,endTask
,startFunction
, andendFunction
.getTicksPerSecond
–– Returns the CPU clock frequency of the target hardware.
For more information about the methods, see Parser Definition and Usage Example.
Register the parser:
Create a
target.TracerService
object and specify these properties:Name
– Name that you use to call tracer.MATLABClass
– Name of parser class, including namespace.Attributes
– List of additional information that the parser requires. Each attribute is atarget.TracerAttributes
object that has a key.
Register your parser by adding the object to an internal database.
For example:
tc = target.create('TracerService', ... 'Name','myParserName'); tc.MATLABClass = "fullPathToMyParser"; tc.Attributes(1) = target.create('TracerAttributes'); tc.Attributes(1).Key = 'AddressFile'; target.add(tc);
Use the parser to generate execution-time metrics:
Set up your target hardware to generate a code execution trace. This step is specific to the target hardware and depends on the target hardware configuration in MATLAB and tracer functionality.
Run a PIL simulation, which produces the code execution trace.
Invoke the
coder.profile.trace.parse
function, which applies the parser to the code execution trace, creating acoder.profile.ExecutionTime
object. When you invoke the function, you must specify your parser, thecodedescriptor.dmr
filepath, the trace file, and other files that your parser requires. For example:executionProfile = coder.profile.trace.parse('ExampleParser', ... fullfile('SILTopModel_ert_rtw', 'codedescriptor.dmr'), ... 'trace.csv', ... 'AddressFile', 'address.csv')
During a PIL simulation, only one task is active at a time because tasks are executed in isolation. If the profiling event order is not followed during processing of the
coder.profile.ExecutionTime
object, the software generates warnings.View the execution-time metrics in the Code Profile Analyzer:
coder.profile.show(executionProfile);
Parser Definition and Usage Example
This example aims to provide more details about implementing and using a custom parser.
Assume that your tracer generates two CSV files at the end of the PIL simulation:
trace.csv
— Provides the main execution trace, using the line formatevent,functionAddr,time
:event
is the function start or function return.functionAddr
is the function address in the code.time
is the time at which the event occurs.
address.csv
— Provides function addresses of named functions in the code, using the line formatfunctionAddr,functionName
:functionAddr
is the function address in the code.functionName
is the name of the function.
For simplicity, this example considers only a few function calls and one simulation step as shown in this table.
Generated File | File Content |
---|---|
trace.csv | start,0x0040117a,1000 |
start,0x004001ec,1010 | |
start,0x004001ec,1020 | |
start,0x00402492,1030 | |
start,0x0040247c,1040 | |
end,0x0040247c,1050 | |
end,0x00402492,1060 | |
end,0x004001ec,1070 | |
end,0x004001ec,1080 | |
end,0x0040117a,1090 | |
address.csv | 0x00402492,step |
0x0040247c,CounterTypeA | |
0x0040117a,xilRun | |
0x004001ec,xilOutput | |
0x004001ec,xilProcessMsg |
This table shows how you can implement your parser.
coder.profile.trace.Parser Object | Comments |
---|---|
classdef MyParser < coder.profile.trace.Parser | Example implementation of
|
properties (Access=private)
FcnAddressMap;
end
| Private properties. |
methods (Access = protected) | Protected method implementations. |
function res = getTicksPerSecond(~) res = 200000000; end | Required.
|
function processCustomFields(this, logger, options) arguments this ~ options.AddressFile (1,:) char end fid = fopen(options.AddressFile ); c = onCleanup(@()fclose(fid)); this.FcnAddressMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); while true tline = fgetl(fid); if ~ischar(tline) break; end % split line parts = strsplit(tline, ','); % get address and function name hexAddr = parts{1}; fcnName = parts{2}; this.FcnAddressMap(hexAddr) = fcnName; end end | Required.
The method:
|
function parseTrace(this, traceFileName, logger) % parse the main execution trace to extract code execution isTaskRunning = false; % start parsing fid = fopen(traceFileName); c = onCleanup(@()fclose(fid)); while true tline = fgetl(fid); if ~ischar(tline) break; end % check if it is a valid trace line parts = strsplit(tline, ','); event = parts{1}; hexAddr = parts{2}; timer = str2double(parts{3}); % process sample if ~this.FcnAddressMap.isKey(hexAddr) continue; end fcnName = this.FcnAddressMap(hexAddr); if ~isTaskRunning % the CPU is idle, no production code running if logger.isTask(fcnName) assert(strcmp(event, 'start')); isTaskRunning = true; logger.startTask(fcnName, timer); end else % the CPU is not idle if strcmp(event, 'start') logger.startFunction(fcnName, timer); else if logger.isTask(fcnName) logger.endTask(fcnName, timer); isTaskRunning = false; else logger.endFunction(fcnName, timer); end end end end end | Required.
The
In this example, the method discards all functions called before and after a task execution as they are not part of the generated code. |
end |
|
end |
|
After implementing your parser, register the parser in MATLAB.
tc = target.create('TracerService', 'Name', 'ExampleParser'); tc.MATLABClass = "MyParser"; tc.Attributes(1) = target.create('TracerAttributes'); tc.Attributes(1).Key = 'AddressFile'; target.add(tc);
To create the coder.profile.ExecutionTime
object, run the
parser.
executionProfile = coder.profile.trace.parse('ExampleParser', ... fullfile('SILTopModel_ert_rtw', 'codedescriptor.dmr'), ... 'trace.csv', ... 'AddressFile', 'address.csv')
To view execution-time metrics in the Code Profile Analyzer, run:
coder.profile.show(executionProfile);
To open the profiling report, run:
report(executionProfile)
For this example, consider section 3 of the profiling report.
The report section shows that the step
method is executed only once
and takes 150 ns. For a clock frequency of 200 MHz, the time taken corresponds to 30 cycles,
which is the difference between the time values in the 0x00402492
(step
) lines in trace.csv
, that is
start,0x00402492,1030
and end,0x00402492,1060
.
CounterTypeA
runs for 50 ns or 10 cycles, which is the time difference
between start,0x0040247c,1040
and
end,0x0040247c,1050
.