В прошлой статье: Код, учитывающий временные погрешности , был поднят вопрос о количестве тактов на операции в в контроллерах платформы 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й Вариант..