Измерение загрузки контроллера можно условно разделить на:
- измерение нагрузки процессора. Показывает справляется ли вся система в целом: Windows + TwinCAT.
- Измерение времени исполнения программы в текущем цикле. Укладывается ли текущая ветвь программы в заданное время цикла.
- Профилирование. Замер времени выполнения отдельных частей программы.
И возвращаясь к теме предыдущего поста: "измерять производительность — это хороший способ узнать, осталось ли что-нибудь за кадром. Иногда это просто любопытство".
Разбор программы
PROGRAM MAIN | |
VAR | |
PAYLOAD: UINT := 100; | |
usage, usageMax: UDINT; | |
latencyMax, latencyAct: UDINT; | |
cycleExecTime, cycleTimeSti, cycleExecSti: REAL; | |
cycleLoad, cycleLoadUser: REAL; | |
TcSysLatency: TC_SysLatency; | |
TcCpuUsage: TC_CpuUsage; | |
CpuCounter: GETCPUCOUNTER; | |
i: UDINT; | |
a: LREAL := 3.1415926; | |
b: LREAL := 2.7182818; | |
cpustart: UDINT; | |
Timer: TON := (PT := T#10s); | |
sti: SYSTEMTASKINFOTYPE; | |
END_VAR | |
cycleExecTime := (CpuCounter.cpuCntLoDW - cpustart) / 10000.0; | |
cycleLoad := sti.lastExecTime / (sti.cycleTime / 100.0); | |
cycleLoadUser := (CpuCounter.cpuCntLoDW - cpustart) / (sti.cycleTime / 100.0); | |
CpuCounter(); | |
cpustart := CpuCounter.cpuCntLoDW; | |
sti := SystemTaskInfoArr[1]; | |
cycleExecSti := sti.lastExecTime / 10000.0; | |
cycleTimeSti := sti.cycleTime / 10000.0; | |
TcCpuUsage(START:= TRUE); | |
TcSysLatency(START:= TRUE); | |
IF NOT TcSysLatency.BUSY THEN | |
TcSysLatency(START:= FALSE); | |
latencyMax := TcSysLatency.MAXIMUM; | |
latencyAct := TcSysLatency.ACTUAL; | |
END_IF | |
Timer(IN := NOT Timer.Q); | |
IF Timer.Q THEN | |
usageMax := 0; | |
END_IF | |
IF NOT TcCpuUsage.BUSY THEN | |
TcCpuUsage(START:= FALSE); | |
usage := TcCpuUsage.USAGE; | |
usageMax := MAX(usageMax, usage); | |
END_IF | |
FOR i := 0 TO PAYLOAD DO | |
a := a / b; | |
a := a * b; | |
END_FOR | |
CpuCounter(); | |
END_PROGRAM |
В программе выше используются функции: TC_SysLatency, TC_CpuUsage, GETCPUCOUNTER. Также используется информация из встроенного глобального массива SystemTaskInfoArr[]. Он предоставляет структуру данных SYSTEMTASKINFOTYPE.
TC_SysLatency пропустим, я по прежнему не вижу в нем смысла. TC_CpuUsage возвращает целое число процентов нагрузки на процессор. Это значение должно совпадать с графиком в System Manager, но это не точно и это было видно выше.
GETCPUCOUNTER работает независимо от счетчика в CPU. Это счетчик 100 наносекундных циклов. Увеличение на единицу соответствует прошедшему времени в 100 нс. Увеличение на 10 соответствует 1 микросекунде. Посмотрим как перевести в миллисекунды с десятичными долями:
52'108'000 наносекунд = 52'108'0 100*нс = 52'108 микросекунд = 52,108 миллисекунд.
LREAL cpuCntMs := (cpuCntLoDW + cpuCntHiDW) / 10000.0
Счетчик можно использовать для профилирования времени выполнения отдельных блоков программы внутри цикла. Можно узнать сколько времени занимает выполнение подпрограммы или функционального блока.
SystemTaskInfoArr[] в том же цикле отдает структуру SYSTEMTASKINFOTYPE. Индексом для массива служит номер текущей задачи. Индекс можно получить с помощью функции GETCURTASKINDEX в этом же цикле и начать его использовать уже в следующей строке программы.
Структура SYSTEMTASKINFOTYPE содержит состояние предыдущего цикла. Эта структура содержит:
- cycleTimeExceeded — TRUE = время цикла превышено, FALSE = время выполнения в норме. Значение параметра не фиксируется, в каждом цикле оно может быть разным. Всё зависит от времени исполнения предыдущего цикла.
- cycleTime — максимально возможное время на выполнение цикла. Задается в сотнях наносекунд. Для перевода в миллисекунды, разделите значение cycleTime на 10000.0.
- lastExecTime — время выполнения программы в предыдущем цикле. Предыдущего, потому что текущий цикл еще только выполняется, вот прямо сейчас, в данный момент. Значение в сотнях наносекунд.
- cycleCount — номер текущего цикла от момента включения контроллера. Если номер цикла умножить на длительность цикла cycleTime, можно узнать сколько прошло времени с момента включения контроллера.
В TwinCAT 3 массив поменял имя на _TaskInfo[], а структура стала более подробной и теперь называется PlcTaskSystemInfo.