В прошлой статье: Код, учитывающий временные погрешности , был поднят вопрос о количестве тактов на операции в в контроллерах платформы Arduino. Стало интересно провести исследование этого вопроса и вот, что из этого получилось
Количество тактов равно длительности процесса в микросекундах умноженной на количество тактов за 1 микросекунду, для большинства современных ардуино-плат это количество равно 16. Для определения длительности процесса в микросекундам мы будем применять функцию micros(), которая возвращает количество микросекунд с момента начала выполнения программы. В общем случае процесс можно представить так:
unsigned long L1 = 0; unsigned long Kol_Takt = 0; ...... L1 = micros(); //Здесь располагается операция для которой необходимо выполнить вычисление Kol_Takt = (micros() - L1) * 16;
Как видите все очевидно и просто, но этот метод не учитывает, одно обстоятельство эти стороки:
L1 = micros(); Kol_Takt = (micros() - L1) * 16;
то же требуют времени на выполнение, т.е. для более точного вычисления необходимо определить это время (и соответствующее ему количество тактов) и отнять его от конечного результата вычисления. Это можно сделать примерно так:
unsigned long L1 = 0; unsigned long L2 = 0; unsigned long Kol_Takt = 0; ...... L1 = micros(); L2 = micros()-L1;//Длительность пустой операции L1 = micros(); //Здесь располагается операция для которой необходимо выполнить вычисление Kol_Takt = (micros() - L1 - L2) * 16;
Как я выше говорил, результат выполнения может не всегда предсказуем и поэтому необходимо вычислять среднестатистическое значение, ниже я приведу исходник, вычисляющий количество тактов для операции суммирования переменных типа byte.
//Переменная для вычисления
byte x1 = 1;
//Вычислительные переменные
unsigned long L1 = 0;
unsigned long L2 = 0;
unsigned long L3 = 0;
unsigned long L4 = 0;
unsigned long L5 = 0;
//Переменная для хранения количества операций в вычислительных циклах
unsigned long kol = 1000;
boolean b = true;
void setup()
{
Serial.begin(115200);
}
void loop()
{
if (b)
{
for (int i = 0; i<= kol; i++)//Вычислительный цикл
{
L1 = micros();
L2 = micros()-L1;//Вычисление длительности пустой операции
x1+=x1;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2;//Вычисление длительности операции
L4 += L3;//Добавление длительности операции к счеткику общей длитильности
}
Serial.print(" byte + byte ");
Serial.println(L4/kol*16);
b = false;//Что бы не было повторгоно входа в участок кода
}
}
Результатом работы данного скетча является следующая строка:
byte + byte 64
т.е. для выполнения суммирования двух переменных типа byte необходимо 64 такта времени, но на самом деле это не совсем так, дело в том что функция micros() определяет длительность с разрешением 4 микросекунды, т.е. меньше 64 (4 * 16), невозможно получить результат, но не так много операций имеет подобную скорость выполнения и дальше мы с вами с этом убедимся, но стоит отметить что ответ программы 64 означает, что реальное значение может простираться от 1 до 64 тактов, соответственно если ответ 128 то значение равно 65-128, если 960 то 897-960 и так далее...
Далее я приведу исходник вычисляющий длительности операций: сложения, вычитания, умножения и деления; для типов byte, int, long и float. Выполнив скетч вы сможете сравнить эти значения, которые будут выведены в виде простенькой таблицы. Исходный текст будет представлен в плохоформализованном виде, т.е. я отказался от стандартных отступов в пользу компактности исходного кода, но думаю проблемы в понимании исходника не будет.
/*Переменные для вычисления*/
byte x1 = 1;
int x2 = 1;
long x3 = 1;
float x4 = 1;
unsigned long L1 = 0;
unsigned long L2 = 0;
unsigned long L3 = 0;
unsigned long L4 = 0;
unsigned long L5 = 0;
unsigned long L6 = 0;
unsigned long Total = 0;
boolean b = true;
//Переменная для хранения количества операций в вычислительных циклах
int kol = 10000;
void setup()
{
Serial.begin(115200);
}
void loop()
{
if (b)//Если таблица не выводилась
{
Total = micros();//Сохранение количества тактов вначале вычислительного цикла
Serial.println("________________________________________________");
Serial.println("| Operators | Tick count of the controller |");
Serial.println("------------------------------------------------");
/*Расчет количества тиков для математических операций с типом byte*/
/*Вычисление для byte + byte*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x1+=x1;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" byte + byte | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для byte - byte*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x1-=x1;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" byte - byte | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для byte * byte*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x1*=x1;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" byte * byte | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для byte + byte*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x1/=x1;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" byte / byte | ");Serial.println(L5*16); L6+=L5;
Serial.println("");
/*Расчет количества тиков для математических операций с типом int*/
/*Вычисление для int + int*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x2+=x2;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" int + int | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для int - int*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x2-=x2;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" int - int | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для int * int*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x2*=x2;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" int * int | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для int / int*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x2/=x2;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" int / int | ");Serial.println(L5*16); L6+=L5;
Serial.println("");
/*Расчет количества тиков для математических операций с типом long*/
/*Вычисление для long + long*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x3+=x3;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" long + long | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для long - long*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x3-=x3;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" long - long | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для long * long*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x3*=x3;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" long * long | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для long / long*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x3/=x3;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" long / long | ");Serial.println(L5*16); L6+=L5;
Serial.println("");
/*Расчет количества тиков для математических операций с типом float*/
/*Вычисление для float + float*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x4+=x4;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" float + float | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для float - float*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x4-=x4;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" float - float | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для float * float*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x4*=x4;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" float * float | ");Serial.println(L5*16); L6+=L5;
/*Вычисление для float / float*/
for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
x4/=x4;//Операция для которой вычисляется количество тиков
L3 = micros()-L1-L2; L4 += L3;}L5 = L4/kol; L4 = 0;
Serial.print(" float / float | ");Serial.println(L5*16); L6+=L5;
Serial.println("");
/*Расчет и вывод общих значений*/
Serial.println("------------------------------------------------");
//Количество тиков затаченное на вычислительные циклы
Serial.print("Expenditure for the calculation of ticks: ");
Serial.println(L6*16*kol);
//Количество тиков на остальные операции (вывод, подготовка данных)
Serial.print(" Intermediate operations: ");
Serial.println((micros()-Total)*16-L6*16*kol);
//Всего тиков за весь процесс
Serial.print(" Total spent ticks: ");
Serial.println((micros()-Total)*16);
b = false;//Таблица выводилась
}
}
Результать работы скетча имеет вид

Аналогичным образом вы можете вычислить количество тактов для любого кода вашей программы. Удачи вам.
ЗЫ: Обратите внимание на то что размер кода в скетче составил 12206 байт, что недопустимо для контроллеров ATMega 8, поэтому владельцам подобных плат придется упростить исходник..

0 комментариев на «“Вычисление количества тактов контроллера для операции”»
Пожалуй, полезно иметь представление о том, сколько времени занимает операция.
Подправьте, там пара копичаток, выводится byte / byte, а считается byte * byte
Спасибо, большое! Исправил исходник и скрин.
Правильно ли я понимаю, что множитель 16 происходит от 16МГц? Т.е. Если я работаю на 8МГц’ах, то множитель будет 8.
Существует 2 варианта.
1й Вариант — вы используете 16-ти мегагерцный контроллер например ATMega1280 16AU и на нем установлен кварцевый резонатор 8МГц. В этом случае ваш контроллер будет неверно исчислять временые интервалы с помощью функций delay и delayMicrosecunds (делая паузы в двое более длинные), но при этом аппаратно будет выполнять все действия точно так же, т.е. выдавать значения micros() с дискретностью 4 микросекунды (которые реально будут 8 микросекунд), но количество тактов будет тем же, и множитель будет 16.
2й Вариант — вы используете 8-ми мегагерцный контроллер например ATMega1280 8AU, в этом случае аппаратные делители вычисляющие реальные отрезки времени будут иметь вдвое меньшие значения, что позволит функциям delay и delayMicrosecunds точно вычислять временые интервалы, но при этом для потдержания аппаратной совместивости контролеров функция micros() будет выдавать значения так же с дискретностью 4 микросекнды (реальный а не как в первом случае равным 8-ми), а 4 микросекунды для данного контролера будут соответствовать множителю 8.
Я использую МК ATMega8L-8PU на встроенном резонаторе в 8МГц
2й Вариант..