Время выполнения отдельных команд Arduino
- Войдите на сайт для отправки комментариев
При программировании тех или иных критичных ко времени выполнения фрагментов кода нужно уметь оценить время выполнения отдельных команд точнее, чем "арифметические операции над числами с плавающей запятой выполняются существенно медленнее, чем над целыми." ( http://arduino.ru/Reference/Float ).
Ниже приведены результаты замеров времени выполнения некоторых конструкций и команд:
запись байта в память - 2 такта запись 2 байтов - 4 такта запись 4 байтов - 8 тактов цикл int (long) - 6 тактов int ADD, SUB, MUL - 6-7 тактов int DIV - 235-245 тактов long ADD, SUB, MUL - 15-17 тактов (22-23) long DIV - 670-680 тактов int => float - 70-72 тактов float ADD, SUB, MUL - 200-220 тактов float DIV - 550-560 тактов sin(float) - 2000 тактов / 124 мкс millis - 2 мкс micros - 4 мкс digitalWrite - 6-8 мкс, Mega - 8-9 mcs digitalRead - 4-6 мкс, Mega - 7 mcs pinMode - 5 мкс analogRead - 111-112 мкс shiftOut - 180-185 мкс, Mega - 210
Измерения проводились для двух версий Arduino Uno 328, различающихся конструктивом чипа, Nano 328, а также Mega 2560. В целом результаты совпали, но для Меги отдельные команды (в основном - работы с портами) оказались чуть медленнее.
Интересный результат - независимость времени цикла от типа переменной цикла. Однако, подозреваю, что при константных пределах цикла оптимизатор мог попросту заменить один тип на другой, тем более, что, как показали измерения, время выполнения арифметических операций с long примерно в 2.5-3 раза больше времени выполнения аналогичных операций с int.
float, конечно, в среденм обрабатывается заметно дольше, чем целочисленные типы, но за одним исключением: деление long выполняется дольше. А т.к. деление вообще выполняется намного дольше других операций, иногда может иметь смысл производить вычисления не в long, а в float.
Из тригонометрии исследовался только синус, но и этого результата, мне кажется, достаточно для оценки прогизводительности тригонометрических функций.
Люблпытно, что команда запроса времени micros() выполняется вдвое дольше, чем millis(), хотя, по логике следовало бы сделать это наоборот.
Кстати, кто не знает, micros() округляет показания до ближайшей "четверки", т.е. результат ее выполнения всегда делится нацело на 4. Это для тактовой частоты 16 МГц. Для 8 МГц - на 8, т.е. результатом можкт быть 8, 16, 32, 48, 64, 80, 96..., но никак не 28 или 60.
Работа с портами происходит достаточно быстро, если не считать АЦП (analogRead()) и shiftOut(). В общем, ничего удивительного: обе команды имеют внутри цикл.
Но, вообще-то, самой медленной командой, пожалуй, следует считать работу с COM-портом, т.е. Serial.print(). В данном случае я ее не анализировал чтобы не засорять вывод результатов в этот самый порт, но примерно можно оценить так, что при скорости 9600 выврд одного символа занимает порядка 1000 мкс. Это оценка снизу.
Измерения выполнялись при помощи этого скетча.
// Time measurement programm #include <avr/pgmspace.h> #define ml8 1320 // buffer size in bytes (byte, char) #define ml16 ml8/2 // buffer size in 2-bytes words (int) #define ml32 ml8/4 // buffer size in 4-bytes words (long, float) union buff_union { byte buff_b[ml8]; int buff_i[ml16]; long buff_l[ml32]; float buff_f[ml32]; }; buff_union bu; void setup() { Serial.begin(115200); pinMode(3, OUTPUT); pinMode(11, OUTPUT); } void PrintRes(float a, int i, const __FlashStringHelper* str) { Serial.print(str); Serial.print(a); Serial.print(F(" mcs\t")); Serial.print(a*16); Serial.print(F(" cycles\t")); Serial.println(i); } void loop() { Serial.print(F(" Time Measurements, byte size:\t")); Serial.print(ml8); Serial.print(F(",\tint size:\t")); Serial.print(ml16); Serial.print(F(",\tfloat size:\t")); Serial.println(ml32); int i; // (01) fill bytes long t0 = micros(); for(i = 0; i < ml8; i++) { bu.buff_b[i] = 3; } long t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("Fill bytes:\t")); // (02) fill bytes long j; t0 = micros(); for(j = 0; j < ml8; j++) { bu.buff_b[j] = 3; } t1 = micros(); PrintRes((float)(t1-t0)/j, (int)j, F("Fill bytes(L):\t")); // (03) fill intrgers t0 = micros(); for(i = 0; i < ml16; i++) { bu.buff_i[i] = 312; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("Fill ints:\t")); // (04) fill long t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_l[i] = 123312; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("Fill longs:\t")); // (05) fill float t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_f[i] = 1.234567; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("Fill floats:\t")); // (06) run millis (byte) t0 = micros(); for(i = 0; i < ml8; i++) { bu.buff_b[i] = (byte)millis(); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("millis(b):\t")); // (07) run millis (int) t0 = micros(); for(i = 0; i < ml16; i++) { bu.buff_i[i] = (int)millis(); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("millis(i):\t")); // (08) run millis (long) t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_l[i] = millis(); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("millis(L):\t")); // (09) run micros (long) t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_l[i] = micros(); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("micros(L):\t")); // (10) int mul t0 = micros(); for(i = 0; i < ml16; i++) { bu.buff_i[i] = i*37; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("int MUL:\t")); // (11) int div t0 = micros(); for(i = 0; i < ml16; i++) { bu.buff_i[i] = 32719/(i+1); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("int DIV:\t")); // (12) int add t0 = micros(); for(i = 0; i < ml16; i++) { bu.buff_i[i] = 21719 + i; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("int ADD:\t")); // (13) int sub t0 = micros(); for(i = 0; i < ml16; i++) { bu.buff_i[i] = 32719 - i; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("int DIV:\t")); // (14) long mul t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_l[i] = i*37831; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("long MUL:\t")); // (15) long div t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_l[i] = 2123456789/(i+1); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("long DIV:\t")); // (16) long add t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_l[i] = 2123456789 + i; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("long ADD:\t")); // (17) long sub t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_l[i] = 2123456789 - i; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("long SUB:\t")); // (18) int => float t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_f[i] = i; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("int => float:\t")); // (19) float mul t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_f[i] = i*378.31; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("float MUL:\t")); // (20) float div t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_f[i] = 212345.6789/(i+1); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("float DIV:\t")); // (21) float add t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_f[i] = 212345.6789 + i; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("float AD:\t")); // (22) float sub t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_f[i] = 2123.456789 - i; } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("float SUB:\t")); // (23) float sin t0 = micros(); for(i = 0; i < ml32; i++) { bu.buff_f[i] = sin(0.123*i); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("sin(float):\t")); // (24) digitalWrite t0 = micros(); for(i = 0; i < ml8; i++) { digitalWrite(2, bu.buff_b[i] && 1); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("digitalWrite:\t")); // (25) digitalWrite t0 = micros(); for(i = 0; i < ml8; i++) { digitalWrite(11, bu.buff_b[i] && 1); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("digitalWrite:\t")); // (26) digitalRead t0 = micros(); for(i = 0; i < ml8; i++) { bu.buff_b[i] = digitalRead(2); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("digitalRead:\t")); // (27) digitalRead t0 = micros(); for(i = 0; i < ml8; i++) { bu.buff_b[i] = digitalRead(10); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("digitalRead:\t")); // (28) analogRead t0 = micros(); for(i = 0; i < ml16; i++) { bu.buff_i[i] = analogRead(A0); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("analogRead:\t")); // (29) pinMode t0 = micros(); for(i = 0; i < ml16; i++) { pinMode(3, i && 1); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("pinMode:\t")); // (30) shiftOut t0 = micros(); for(i = 0; i < ml16; i++) { shiftOut(3, 11, LSBFIRST, i && 0xff); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("shiftOut:\t")); // (31) delayMicroseconds t0 = micros(); for(i = 0; i < ml8; i++) { delayMicroseconds(1); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("delayMs(1):\t")); // (32) delayMicroseconds t0 = micros(); for(i = 0; i < ml8; i++) { delayMicroseconds(10); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("delayMs(10):\t")); // (33) delayMicroseconds t0 = micros(); for(i = 0; i < ml8; i++) { delayMicroseconds(100); } t1 = micros(); PrintRes((float)(t1-t0)/i, i, F("delayMs(100):\t")); delay(15000); }
При написании аналогичных программ следует учитывать, что Arduino IDE использует оптимизирующий компилятор, который "вырезает" все "ненужные" вычисления, т.е. вычисления, результаты которых не используются в программе.
Для справки:
https://www.gnu.org/savannah-checkouts/non-gnu/avr-libc/user-manual/benchmarks.html
Длительность команд ЦПУ хорошо описана в соответствующих книжках по AVR. Не помню откуда выкачивал avr-sys.pdf "Система команд 8-и разрядных RISC контроллеров семейства AVR" - даны потактовки для каждой команды для всех режимов.
Чтение/запись в память идет за 2 такта согласно скорости работы SRAM, и на Мега2560 может идти и 3 такта (до 6), если к ней подключено расширение SRAM внешней память (до 64 килобайт напрямую) через имеющийся у неё x-bus интерфейс (занимает 19 ног), И если обращение идет к адресам, превышаюшим 8200 байт SRAM (для внутренних адресов всегда 2 такта). Настраивается при работе с x-bus. Соответственно, нет никакой нужды измерять команды ЦПУ.
Кстати, x-bus позволяет и легко расширить память меги. Компилятору достаточно только указать верхний допустимый предел в 64К или сколько поставили.. в Сети есть схемы, легко расширяющие до 512к, себе рисовал до 2М на 3 корпусах 74 серии и плюс 2 ноги меги.. но делать пока не стал, ибо микросхема памяти, из-за которой и спроектировал - оказалась банально битой.
Ну а по скорости выполнения функций Wiring ... что есть, то есть. Каждый вполне способен сам свести digitalWrite() или analogWrite() до 2-х тактов (0.13мксек). Скорость analogRead() в 8-16мкс, тоже является "штатной" без учета времени разогрева ADC, указанного в даташите как 125кгц скорость аналогового чтения, а при снижении разрядности может достигать более 1 Мгц (1мксек) - регулируется настройками ADC.
micros() и все остальные типовые решения подсчета времени на базе 0-го таймера - да, считают микросекунды по 4шт. Но, это не обязательно так. У меня есть micros() с точностью до 0.5мксек и менее - счел нецелесообразным из-за пожирания процессорного времени процедурой обработки прерываний от таймера. Но .. можно настроить (зачем только?).
Ну и советую не забывать, что штатная функция от Wiring() - millis() считает время "скачками" по 42 миллисекунды и оно плавает на плюс-минус 1 миллисекунду с этим периодом. Из-за этого, не бывает 42, 84 .. и т.д. "миллисекунд". После 41 всегда идет 43. :)
А в целом, спасибо за оценку времени исполнения функций Wiring...
2 такта на память, как понимаю из-за того, что команды чтения/записи содержат в себе адрес, то есть занимают 2 слова, а не одно. или при косвенном обращении надо выложить на шину содержимое регистра дополнительным тактом.
Из моих расчетов по даташитам:
Время выполнения без учета вызова функций:
1. Процедура обработки прерывания Т0: у Wiring = 3.5мксек, можно свести к 2.5мксек, за счет отказа от ихней millis()
2. micros() = 2.625 мксек, и "лучше" не получается.
Длительность команд ЦПУ хорошо описана в соответствующих книжках по AVR.
...
Соответственно, нет никакой нужды измерять команды ЦПУ.
Я, кажется, нигде не писал об AVR и не ставил такой цели. Я писал об Arduino, который может быть на различных платформах. Да, в проведенных мной тестах принимали участие только AVR, но никто не мешает запустить этот код, скажем, на Duo (изменив строчку с include) и поучить актуальные результаты для другой архитектуры.
У меня есть micros() с точностью до 0.5мксек и менее - счел нецелесообразным из-за пожирания процессорного времени процедурой обработки прерываний от таймера. Но .. можно настроить (зачем только?).
Ну и советую не забывать, что штатная функция от Wiring() - millis() считает время "скачками" по 42 миллисекунды и оно плавает на плюс-минус 1 миллисекунду с этим периодом. Из-за этого, не бывает 42, 84 .. и т.д. "миллисекунд". После 41 всегда идет 43. :)
Мне вообще не нравится, как сделан учет времени в Arduino. Я бы редпочел структуру длиной не менее 6 байт с "единицей" в районе 10-20 мкс и возможностью получать 4-байтовое число тремя функциями: байты с 0 по 3, с 1 по 4 и со 2 по 5. Т.е. одно в единицах пусть 16 мкс, второе - 4 мс и третье - 1 с (это примерно, все - степени 2).
До исследования свойств millis() и micros() я пока не добрался, но сильно подозреваю, что они друг с другом не связаны, т.е. нельзя запросить micros() и получить дробную часть millis(). НО как-нибудь до этого доберусь. Для этого, минимум, надо продключить внешние RTC. Тем более, Вы подсказали, в каком направлении искать.
Да, в Wiring они никак по сути не связаны. micros() получает значение микросекунд непосредственно из структур данных учета времени ovf_count и регистра таймера Т0 с учетом его возможного переполнения, но ещё не обработанном прерывании. Как оказалось это важно. У меня сделано точно также, и по сути получилось "байт в байт".
millis() там работает практически независимо, исключительно на данных от процедуры обработки прерываний. Зато быстро. Но, из-за обеспечения корректной работы millis(), процедура обработчика перегружена: она считает "накопление" ошибки времени для ускорения millis(). Потому как счетчик переполнений ovf_count переполняется каждую 1024 микросекунду, а не каждую 1000. Вот эти 24 микросекунды и приходится накапливать и потом изменять время millis() скачками.
Поэтому у себя отказался от фракционного накопления, а все измерения делаю прямым консистентным чтением счетчика переполнений есть макрос getOvfCount(). Быстро, дешево и практично. Неудобство только в том, что время бежит по 1024 микросекунды, и константы задержек приходится пересчитывать вручную домножая миллисекунды на 0.976 на куркуляторе. Сделал себе millis() тоже, но на целочисленном делении, поэтому медленно и практически оказалась не нужна. Счетчика переполнений хватает "за глаза".
Да, ещё есть замечательные функции _delay_loop_1() и _delay_loop2() в самой библиотеке AVR. Они считают микросекундные задержки с высокой точностью ассемблерными вставками. Первая принимает байтовое значение по 3 микросекунды, а вторая 16бит по 4 микросекунды.
Немного не понятно, проясните, пожалуйста, какое отличие в тактах у операций, к примеру:
и
?
x << 12= x * 4096
Очень приятно общаться с образованными людьми, но Вы не поняли.
Я хочу узнать, чтоит ли оптимизировать программу под замену умножения сдвигами и что я на этом выиграю.
Например, в этой теме - http://arduino.ru/forum/programmirovanie/zapusk-funktsii-s-zadannoi-chastotoi#comment-186323 вместо задердки в 4 секунды, я стремился выполнять пересчет частоты оборотов раз в 3.750 секунды только для того, чтобы при расчете нужно было количество опознаных оборотов умножать на 16, а не на 14.65
Загрузил скетч из стартового сообщения на две ардуинки. Вот что получилось
В общем совершенно одинаково для двух ардуинок.
Я хочу узнать, чтоит ли оптимизировать программу под замену умножения сдвигами и что я на этом выиграю.
Лет десять назад - стоило. Сейчас - однозначно нет: современные компиляторы достаточно умны, чтобы в подавляющем большинстве случаев сгенерировать оптимальный код. И скорее всего, все ваши попытки оптимизировать окажутся ничуть не лучше встроенных в компилятор возможностей, в итоге - только время потратите.
Безусловно, это лишь общие слова, конкретика бывает разной, но в общем, опять же случае - premature optimization is the root of all evil (c) Дональд Кнут, емнип.
В общем совершенно одинаково для двух ардуинок.
А что могло быть не одинаково на двух одинаковых микроконтроллерах, работающих на одинаковой тактовой частоте?
Один МК в DIP корпусе, другой в другом, маленьком (не знаю как называется), и второй МК работает на частоте 16000000 на напряжении 3.3 вольта. У первого МК нет А6,А7, а у второго есть. Поэтому я подумал, что может быть есть какая-нибудь разница.
Нет никакой разницы, у них одинаковые ядро и переферия. Тип корпуса и количество выводов на производительность не влияют, они не участвуют в каких-либо расчётах. Напряжение 3,3 вольта тоже не влияет, если МК стабильно работает на той же тактовой частоте (16МГц), что и при 5 вольтах.
Нет никакой разницы, ........ 3,3 вольта тоже не влияет, если МК стабильно работает на той же тактовой частоте (16МГц), что и при 5 вольтах.
Меня терзает вопрос, но мои познания не находят на него ответ.
У меня есть несколько таких ардуинок про мини
Все время использовал их как пятивольтовые, но на днях решил переключить их на 3.3 вольта. Перепаял перемычку и стал питать напряжением 3.3 вольта. Но так и не понял, что на плате меняют перемычки, которые на фотке обозначены Output Voltage Set, и надо ли ее питать напряжением 3.3 в? И что обозначают надписи внизу на правой картинке?
Эта перемычка позволяет подстраивать выходное напряжение линейного стабилизттора и никак не влияет на то, что вы подаете на VCC - скорее, на то, что получите с VCC если на вход RAW подадите питание.
На надписи внизу на "заводе" должны были нанести пометки и обозначить, на какие напряжение и частоту настроена ардуинка. Судя по большому диапазону частот, по пометкам на этих надписях можно было бы судить о том, какой внешний керамический резонатор припаян к плате.
http://microsin.net/programming/avr/beginning-embedded-electronics-part3.html
Послепрочтения этой статьи, я, чё-то, стал думать, сто эта маленьая хренька как раз резонатор, а не кварц.
Эта перемычка ......
Надо просто поподавать, поизмерять.
P.S. Очень порадовал переходничек USB-TTL (не уверен, что правильно назвал...ну которым ардуинку питаю и заливаю скетчи). Так вот....подключил, стал измерять напряжение на VCC. Ардуинка моргнула и перезагрузилась. Измерил еще раз....такая же история. Посмотрел на тестер, а он включен на ток 20А. Короче пару раз сделал коротыш, но все осталось живое....ура!
..... Перепаял перемычку и стал питать напряжением 3.3 вольта. Но так и не понял, что на плате меняют перемычки, которые на фотке обозначены Output Voltage Set, и надо ли ее питать напряжением 3.3 в? И что обозначают надписи внизу на правой картинке?
http://arduino.stackexchange.com/questions/3728/can-i-change-vcc-pin-to-3-3v-on-a-5v-arduino-pro-micro
Перемычка стоит- замкнуты вход и выход стабилизатора.
Перемычка не стоит - стабилизатор работает, а его выходное напряжение почему-то 3.3 Вольта.
Так совпало.
Это обычно так.
Но у вас похоже идёт замыкание части делителя напряжения.
То есть регулировка напряжения стабилизатора.
Где-то так : http://lib.qrz.ru/node/3886
..... Перепаял перемычку и стал питать напряжением 3.3 вольта. Но так и не понял, что на плате меняют перемычки, которые на фотке обозначены Output Voltage Set, и надо ли ее питать напряжением 3.3 в? И что обозначают надписи внизу на правой картинке?
http://arduino.stackexchange.com/questions/3728/can-i-change-vcc-pin-to-3-3v-on-a-5v-arduino-pro-micro
Перемычка .................................
То есть регулировка напряжения стабилизатора.
.................................
.........................................
Но у вас похоже идёт замыкание части делителя напряжения.
То есть регулировка напряжения стабилизатора.
............................................
trembo, спасибо. Проверил, все так как Вы сказали. Если на вход RAW подать напряжение 7-12в, то при левом положении перемычки на входе VCC - 5в, при правом положении перемычки VCC - 3.3в. Т.Е. это имеет значение, если питать нестабилизированным напряжением. А если стабилизированным 3.3в, то будет ардуинка на 3.3в, если стабилизированным 5.0в, то будет ардуинка на 5.0в.
Попробовал свою ардуинку питать напряжением 3.3в, и хоть кварц на ней 16 мегагерц, она работает прекрасно. Но, сразу снимается столько геммороя с подключением дисплеев и модулей, которые любят 3.3в.
Все......перехожу на 3.3в!!!!!
в кто сравнивал по скорости операции с EEROM и SD ???
Очень приятно общаться с образованными людьми, но Вы не поняли.
Я хочу узнать, чтоит ли оптимизировать программу под замену умножения сдвигами и что я на этом выиграю.
Например, в этой теме - http://arduino.ru/forum/programmirovanie/zapusk-funktsii-s-zadannoi-chastotoi#comment-186323 вместо задердки в 4 секунды, я стремился выполнять пересчет частоты оборотов раз в 3.750 секунды только для того, чтобы при расчете нужно было количество опознаных оборотов умножать на 16, а не на 14.65
1. На AVR умножение выполняется аппаратно, так что замиенять на сдвиг в целях оптимизации не стоит.
2. Все рассуждения про уминожения и сдвиги справедливы для целых чисел. Но приведенное Вами 14.65 - число вещественное. Для вещественных чисел ни о какой замене умногжения сдвигами и речи быть не может. Ну и операции над ними выполняются во многол аз дольше, чем над целыми (кроме деления). Так что Вы снсчала определитесь, с какими числеми работаете.
Вот ролик про скорости. Ни чего не понял по английски, но интересно https://www.youtube.com/watch?v=rkIfsYRshRQ
Наверно пора обновить тему. Актуализируем информацию для новых устройств.
OrangPi PC. Код для измерения длительности выполнения операции.
Вывод на экран.
То есть у Вас получилось что 4-х ядерный 1600мгц АРМ всего лишь в 20 раз быстрее дергает ногодрыги? Круть .. :)
Logik, Вам полтора мегагерца "нечем глянуть"?
Протестил на двадцатипятирублевом STM32F030F4P6. Результат -- МК трясет ногой с чаcтотой 8мгц, при работе ядра на 64мгц. Новый проект было лень создавать, врезал тест прямо в свои текущий. Код:
Пруф:
То есть у Вас получилось что 4-х ядерный 1600мгц АРМ всего лишь в 20 раз быстрее дергает ногодрыги? Круть .. :)
Это не у меня "так получилось" это у авторов WiringPi так получилось. Но все достаточно легко обяснимо.
Четырехядерность - хороше, но мое приложение разумеется работает в один поток, остальные 3 ОС использует на системные нужды, т.е. только не мешает приложению. На этом их роль ограничивается. Проц работает не на 1600 а на 1400, что не особо важно, но считаем исходя из этого. Проц оранжа выполняет такты быстрей в 1400/16=87 раз. Код выполняется в 7мс/0,35=20 раз быстрей. Следовательно на один вызов digitalWrite у оранжа уходит в 87/20=4,35 раза больше тактов чем а 328-го. Далее по работе например STM32 известно что 8-битный проц эффективней использует частоту чем 32-битный раза в 2-3. Для определенностипусть 2,5. Остается принять что код digitalWrite у оранжа раза в полтора-два длиней, просто функционала больше, приймем 1,75. А авторы его явно не заморачивались производительностю, в digitalWrite все начинается с проверки нахождения номера пина в допустимом диапазоне, проверки режима настройки и т.д.
Проверяем 20*2,5*1.75=87. Ясно что очень грубая прикидка, но показывает что ничего невероятного нет.
Logik, Вам полтора мегагерца "нечем глянуть"?
Так я прогер, не электронщик (далекое прошлое не в счет), мне серезный осцил дома - моветон ;) То что есть, на STM дискретизирует на 2МГц. Не тянет. Но так даже лучше я подумал. Найти для полутора МГц осцил можна, но при прямой работе с портом есть риск получить 200-400 мегагерц, такой осцил я не найду, пусть уж методика будет одинакова на оба опыта.
Понятно что ничего невероятного нет. Вы просто наглядно подтвердили мой тезис годовалой давности за 4.35 раза, который я тогда сделал "из общих соображений". Из которого, кстати, вытекает что никакой STM32F103 не "порвет" мегу .. даже просто мегу, не говоря уже за иксы.. Спасибки. :)
Ну да. Тогда ж так и порешили, только из общих соображений было 2-3, если правильно помню. Тут еще разрабы добавили сложностю кода до 4 с хвостиком.
Чет я не понял, а как ее рвать надо, эту мегу? На меге три инструкции SBI, CBI и RJMP выполняются 6 тактов, что дает максимальную скорость ногодрыга 2.667мгц. Это несколько меньше восьми мегагерц на стм32, полученных мной в простом эксперименте. Причем, данную скорость выдает сишный код, который хоть и хорошо оптимизируется компилятором, но все равно не идеален. Если зарубиться, то можно еще чуток подкрутить.
Прямая работа с портами оранжа. Код
Выводит:
Для справки, исключительно, напомню: примитивный цикл ногодрыга на Мегах - 3 такта. То есть 5.33Мгц. для 16Мгц и 6.67Мгц для 20Мгц, при разгоне до 24Мгц (некооторые гонят и успешно) можно получить 8Мгц. :)
С учетом того, что надо декрементировать счетчик и делать условное ветвление, ещё + 3-4 такта и все становится в 2 раза хуже для такого теста.
Да, делается на обычном Си с применением макросов из io.h.
Я пока себе плохо представляю, как с помощью инструкций, выполнение которых бы занимало на меге три такта, можно организовать цикл. Если речь идет о последовательном выполнении команд OUT, OUT и команды перехода, то это будет четыре такта, т.к. у меги нет однотактовых команд перехода. Итого, для МК, работающего на чаcтотах 16, 20 и 24мгц, максимальной частотой ногодрыга будут 4, 5 и 6мгц.
Но даже эти дивные мегагерцы оказываются с подвохом, т.к. команды OUT не могут адресовать все пространство и дальше порта E (пишу по памяти, могу чуть напутать с буквой, но общий смысл такой) они не достают. Да и придумать сколь-нибудь осмысленное применение "короткозамкнутого" цикла в реальных приложениях довольно проблематично. Таким образов выходит, что если на специально составленной "синтетике" мега еще может куда-то дернуться (да и то не очень убедительно), то для какого-то более широкого использования все это не очень подходит.
В прямом противпоставлении у меги и вовсе шансов никаких. Реализация того же цикла с миллионом интераций (как в примере для пи и стм32) на меге повлечет драмматическое снижение быстродействия просто из за увеличившегося объема арифметических операций. Мега упадет ниже мегагерца в ногодрыге. Ну и кого тут "рвать" ?
:)
С учетом того, что надо декрементировать счетчик и делать условное ветвление, ещё + 3-4 такта и все становится в 2 раза хуже для такого теста.
Если уж пошло такое дело, что нада выжать максимум частоты, то можна и вечный цикл делать, а выход по прерыванию, например таймера, с подменой адреса возврата в стеке. Экстремальные результаты требуют нетривиальных методов.
Решил еще продолжить, проверить реально ли "10МГц - все что может переферия, и ядро тормозится под неё."
Догрузил ядро командами до 70 тактов на вывод. Теперь код внутри цикла такой.
Имеем 70 нопов, из которых 3 закоментированы. Скорость работы кода не изменилась.
Если раскоментируем 3 последних нопа - скорость станет немного, менее процента, ниже. Вылавливать при скольких именно 2,3 или 4 закоментированых нопах проходит граница я не стал, изменение слабое, надо много возится.
Но похоже что работает так:
1. Непосредственно обращение к порту на запись по указателю занимает порядка 3-х тактов процессора т.е. 0,002мкс
2. Перефирия выполняет вывод за 0,05мкс.
3. При записи в порт до того как он выполнит вывод от предыдущей команды происходит блокировка вызова на время завершения работы периферии.
Итого имеем. Максимальная частота на выходе 10МГц, время вывода константы в порт - около 0,002мкс (здесь может быть заметная погрешность). В пределах цикла вывода частоты остаются около 67+67=134 тактов, т.е. около 0,096мкс, на выполнение других действий, организацию цикла и т.д.
Пожалуй это все что меня интересовало про ногодрыг в оранже.
На 2-ой малинке ногодрыгом до ~42мгц выходит.
Любопытства ради, глянул, какой объем двоичного кода получается для ногодрыга мегой и стм.
Фактически, объем одинаков. Разница в два байта в пользу меги возникла в результате того, что регистр r1 пришел в нуле с какой-то из предыдущих операций и компилятор не стал его повторно инициализировать тем же самым значением. Сам же код, дрыгающий ногами, по объему одинаков, не смотря на то, что в одном случае операции восьмибитные, а в другом тридцатидвухбитные.
Протестил на двадцатипятирублевом STM32F030F4P6. Результат -- МК трясет ногой с чаcтотой 8мгц, при работе ядра на 64мгц.
<del>
Написано в даташите на СТМ32Ф0хх 2 такта, значит два такта. Всё остальное - твои голимые косяки и лишние дыры в голове с утечкой остатка мозга. 2+2=4. 64/4=16МГц. Это максимальная долговременная частота махания ногами для СТМ32Ф0хх. <del>
Для СТМ32Ф2 и Ф4 - 1 такт. <del>.
Далее по работе например STM32 известно что 8-битный проц эффективней использует частоту чем 32-битный раза в 2-3.
<del>
вытекает что никакой STM32F103 не "порвет" мегу .. даже просто мегу, не говоря уже за иксы.. Спасибки. :)
Конечно он её не порвёт, он её просто в ноль помножит. )))))))))))))))))))))
Самая могучая шутка этого форума.
<del>, ты на этом форуме хоть одну строку кода опубликовал?
<del>
<del> неактуально
<del>
<del>
Вау! Активность модератора детектед 8) Спасибо, так бы почаще.
a5021, Цена этой оптимизации - куча адрсного пространства занятая "битовой адресацией" портов ввода/вывода, что существенно ограничивает возможности по использованию адресного пространства в перспективе. Как-бы "микроконтроллерам" стока не надо .. но, я хорошо помню времена, когда 16 метров оперативы х286 считалось "стока не бывает".. не так уж и давно оно було. :) То есть, в перспективе такие (ARM) процессоры - кандидаты на уход в прошлое аналогично х286, и только. Очередной "тупик" в который всех активно загоняют продаваны.. :)