Вычисление количества тактов контроллера для операции

В прошлой статье: Код, учитывающий временные погрешности, был поднят вопрос о количестве тактов на операции в в контроллерах платформы Arduino. Стало интересно провести исследование этого вопроса и вот, что из этого получилось
В общем случае, несмотря на то, что микроконтроллеры строго документированные устройства, распределение вычислительных ресурсов носит не всегда предсказуемый характер. Если например вы выполните цикл содержащий большое количество итераций вы можете обнаружить, что на каждый проход цикла уходит различное время. Это обстоятельство связанно как с тем, что в в теле цикла может происходить условное ветвление, так же может происходить увеличение разрядности переменных (например сточных), а еще на в процессе работы могут возникать ошибки, например связанные с воздействием электромагнитного или ионизирующего излучения, которые могут спонтанно вызывать ложные определения логических уровне (0 или 1). Если же цикл не имеет ветвления и в нем не происходит значительного изменения разрядности, то на долю энтропийных факторов приходится меньше долей процента отклоняющего воздействия. В общем случае для вычисления количества тактов на операцию наиболее удобен метод получения среднестатистического количества тактов, т.е. необходимо выполнить операцию много раз, при этом для каждого прохода вычисляя количество тактов и складывая их в отдельную переменную, после чего необходимо разделить сумму количества тактов на количество выполненных операций.
Количество тактов равно длительности процесса в микросекундах умноженной на количество тактов за 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, поэтому владельцам подобных плат придется упростить исходник..
  • +3
  • 17 декабря 2011, 13:10
  • execom

Комментарии (6)

RSS свернуть / развернуть
+
+1
Пожалуй, полезно иметь представление о том, сколько времени занимает операция.

Подправьте, там пара копичаток, выводится byte / byte, а считается byte * byte
avatar

Ozze

  • 18 декабря 2011, 13:16
+
0
Спасибо, большое! Исправил исходник и скрин.
avatar

execom

  • 18 декабря 2011, 13:38
+
0
Правильно ли я понимаю, что множитель 16 происходит от 16МГц? Т.е. Если я работаю на 8МГц'ах, то множитель будет 8.
avatar

Baxster

  • 21 декабря 2011, 16:51
+
0
Существует 2 варианта.
1й Вариант — вы используете 16-ти мегагерцный контроллер например ATMega1280 16AU и на нем установлен кварцевый резонатор 8МГц. В этом случае ваш контроллер будет неверно исчислять временые интервалы с помощью функций delay и delayMicrosecunds (делая паузы в двое более длинные), но при этом аппаратно будет выполнять все действия точно так же, т.е. выдавать значения micros() с дискретностью 4 микросекунды (которые реально будут 8 микросекунд), но количество тактов будет тем же, и множитель будет 16.
2й Вариант — вы используете 8-ми мегагерцный контроллер например ATMega1280 8AU, в этом случае аппаратные делители вычисляющие реальные отрезки времени будут иметь вдвое меньшие значения, что позволит функциям delay и delayMicrosecunds точно вычислять временые интервалы, но при этом для потдержания аппаратной совместивости контролеров функция micros() будет выдавать значения так же с дискретностью 4 микросекнды (реальный а не как в первом случае равным 8-ми), а 4 микросекунды для данного контролера будут соответствовать множителю 8.
avatar

execom

  • 22 декабря 2011, 00:58
+
0
Я использую МК ATMega8L-8PU на встроенном резонаторе в 8МГц
avatar

Baxster

  • 22 декабря 2011, 04:52
+
0
2й Вариант..
avatar

execom

  • 22 декабря 2011, 07:02

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.