Апгрейд микроконтроллерных отладочных плат для любителей — шаг назад?

Автор: РБМК-1000
[Скачать исходный код программы Tickets]
Здравствуйте, уважаемые читатели! Свыше десяти лет назад — в далёком 2005 году был начат выпуск Arduino — простейшей и недорогой отладочной платы, предназначенной для любительского конструирования микропроцессорных устройств. Arduino представляла собой небольшую печатную плату с микроконтроллером ATMega328 и разъёмами цифровых и аналоговых портов, программирования и питания. Специально для Arduino была разработана простая среда разработки программ, состоящая из текстового редактора, компилятора С++ и утилиты прошивки микроконтроллера. И всё! А больше и не нужно — просто и понятно.


Это был настоящий прорыв — для создания своей микропроцессорной конструкции не обязательно было изготавливать печатную плату, приобретать или собирать программатор, не нужно изучать архитектуру и ассемблер микроконтроллера — программа пишется на языке высокого уровня. Конечно, за предельную простоту разработки пришлось очень здорово платить производительностью — она упала почти на порядок, но благодаря передовому на тот момент микроконтроллеру, остатков вычислительной мощи с запасом хватало для большинства любительских задач. Я думаю, не надо говорить, что Arduino быстро приобрела огромную популярность по всему миру. Со временем, появились новые варианты плат Arduino, с более мощным микроконтроллером, большим количеством портов… Arduino прочно и надолго заняла нишу первых шагов в мир программирования микроконтроллеров.
Технический прогресс не стоит на месте, и в 2011 году появился Raspberry Pi, уже не просто отладочная плата, а целый микрокомпьютер! Raspberry Pi был построена на ARM11 процессоре Broadcom BCM2835 с тактовой частотой 700 МГц и модулем оперативной памяти на 256/512 МБ. Raspberry Pi был предназначен для обучения студентов и школьников программированию, но также как и Arduino приобрёл огромную популярность у любителей. Да, это был второй прорыв в мир программирования, на этот раз уже компьютеров на Linux. Главное, отлично сработала получившаяся преемственность устройств — любитель, имеющий опыт программирования Arduino мог использовать накопленные наработки для программирования микрокомпьютера Raspberry Pi. Вместо восьмибитного 16 МГц микроконтроллера с 2 кб ОЗУ любитель получал 700 МГц ARM1176JZ-F процессор с 256/512 Мб ОЗУ и операционной системой Raspbian на основе Linux, по производительности сравнимый с компьютером на основе Pentium II 300 MHz. Для программирования на борту Raspberry Pi имелись две версии интерпретатора Питон — Phyton 2 и Phyton 3.

К сожалению, прогресс начал сводить на нет применимость любительской технологии. Если любитель по каким-то причинам хотел повторить Arduino, он спокойно изготавливал самодельную двустороннюю плату и запаивал в неё микроконтролер с необходимыми радиодеталями. Такое поделие хоть и могло выглядеть непрезентабельно, но было вполне работоспособно и оно работало. А вот повторение Raspberry Pi для любителя уже стало недостижимо — изготовить четырёхслойную плату можно только в заводских условиях, а запаивать компоненты BGA монтажа придётся как минимум с помощью специальной инфракрасной паяльной станции.
Да, техника здорово шагнула вперёд, даже в России не остались равнодушными и выпустили свой микрокомпьютер МВ77.07 на российском телевизионном процессоре К1879ХБ1Я с точно таким же ядром ARM1176JZ-F, правда, с вдвое меньшей тактовой частотой — всего 324 МГц вместо 700 МГц Raspberry Pi, зато с дополнительным ядром нейропроцессора NMC3 российской разработки! Для российского МВ77.07 были портированы образы Raspbian и на нём работали программы, предназначенные для Raspberry Pi.

Вот оно, наконец-то наступило светлое будущее! Компьютеризация любительских поделок достигла сияющих вершин! Любитель, освоивший Arduino, с приобретением Raspberry Pi делал стремительный рывок вперёд. Старая самодельная метеостанция на Arduino спешно переделывалась под подключённый к интернету Raspberry Pi. Счастливый любитель после апгрейда уже мог прямо на работе со смартфона наблюдать за погодой в доме, и на основе тревожных сводок о стремительно повышающейся влажности, с того же смартфона оперативно вызвать бригаду ремонтников заливающим квартиру соседям.
Какие преимущества любителю даёт переход с Arduino на Raspberry Pi в качестве управляющего устройства? Однозначно, удобство работы — в отличие от отладочной платы микрокомпьютер без каких-либо наворотов можно сразу подключить к интернету и управлять им хоть через полмира! А как же быстродействие? Поскольку, для большинства любительских задач вычислительных мощностей Arduino хватает с запасом, не говоря о Raspberry Pi, то сразу трудно оценить. Ведь в работе переделанной с Arduino на Raspberry Pi метеостанции, кроме удалённого доступа или может сохраняемого журнала работы, ничего не поменялось — как работала, так и работает. Так у какого аппарата будет большее быстродействие — у Arduino с компилятором С++ или Raspberry Pi с интерпретатором Python 2/3? Конечно же на Raspberry Pi… на порядок возросшая вычислительная мощь процессора наверняка перекроет принципиальный недостаток интерпретатора — отсутствие оптимизации? А может, старенькая Arduino не ударит в грязь лицом? Давайте проверим.
Нужен какой-то простой и в то же время наглядный тест производительности. Миллион пустых циклов? Нет, слишком простой. Девять ферзей? Недостаточная сложность, время проверки заведомо не превысит секунды. Тогда остаётся в качестве теста использовать расчёт количества «счастливых» билетов по алгоритму, предложенным Сергеем Тарасовым на сайте Кон-Тики (https://pmk.arbinada.com/ru/node/1240) — полный перебор всего миллиона вариантов и подсчётом попадающихся «счастливых» экземпляров. А почему «счастливых»? Есть старая шуточная примета — если в шестизначном номере автобусного, троллейбусного или трамвайного билета сумма цифр первой половины номера совпадает с суммой цифр второй половины номера, значит билет «счастливый» и можно смело загадывать желание.

Отлично, заставим считать имеющиеся у меня отладочные платы и микрокомпьютеры считать «счастливые» билеты. У меня как раз есть Arduino UNO на ATMega328P, но Raspberry Pi у меня в наличии нет, вместо него тестовые программы будет выполнять русский микрокомпьютер МВ77.07 на К1879ХБ1Я, благо у него такое же процессорное ядро — ARM1176JZ-F. Микроконтроллер ATMega328P построен на базе ядра AVR с производительностью 1 млн.оп./с и работает на частоте 16 МГц, а значит быстродействие у него — 16 млн.оп./с. Микросхема К1879ХБ1Я имеет ядро ARM1176JZ-F с производительностью 1,25 млн.оп./с и работает на частоте 324 МГц — быстродействие 405 млн.оп./с. На первый взгляд — явное преимущество в 25 раз, а если сравнивать с BCM2835 с частотой 700МГц, то и вовсе разница составляет почти 55 раз… А что же покажет эксперимент?
Итак, начнём. Первой будет плата Arduino UNO и её адаптированный к любительскому программированию компилятор С++.

int led = 13;
byte a;
byte b;
byte c;
byte d;
byte e;
byte f;
word g;
boolean h=false;
byte i;
byte j;
byte k;
void setup() {
pinMode(led, OUTPUT);
}
void loop()
{
g=0;
for (a=10; a>=1; a--) {
for (b=10; b>=1; b--) {
i=a+b;
for (c=10; c>=1; c--) {
j=i+c;
for (d=10; d>=1; d--) {
for (e=10; e>=1; e--) {
k=d+e;
for (f=10; f>=1; f--) {
if (j==(k+f)) g++;
}
}
}
}
}
}
if (g==55252)
h=!h;
digitalWrite(led, h);
}

Результат — 0,78 секунд! Я был сильно изумлён, ведь тест миллиона пустых циклов на Arduino UNO у меня выполнялся целых 6 секунд. Давайте разберём эти тесты подробно и рассчитаем количество выполняемых операций. Тест миллион пустых циклов предельно прост:
for (f=1000000; f>=1; f--)
и содерджит три операции — декремент, проверку условия и переход. Итого тест занимает три миллиона операций и выполняется за шесть секунд — итого быстродействие полмиллиона операций в секунду. Эффективность компилятора равна результирующему быстродействию, делённая на быстодействие самого процессора, это буквально коэффициент полезного действия. Для миллиона пустых циклов эффективность компилятора равна 0,5 млн.оп./с разделить на 16 млн.оп./с, итого — 3,1%. Весьма негусто. А что же с подсчётом «счастливых» билетов? Тест содержит шесть циклов, вложенных друг в друга. На выполнение самого внутреннего цикла

for (f=10; f>=1; f--)
{
if (j==(k+f)) g++;
}

тратится 90% процессорного времени. Поэтому, затраты на цикл можно считать за три операции, проверку условия, декремент и переход. Если учесть что количество счастливых билетов всего 5,5% от всего количества (55252), то затратами на инкремент счётчика можно пренебречь, поэтому затраты на проверку можно считать за три операции, сложение, сравнение и переход. Всего 6 операций, + 1 операция на внешние циклы (6/10) и подсчёт билетов. Всего миллион циклов, поэтому можно считать что выполнение теста занимает 7 миллионов операций. Итого быстродействие около 9 млн. оп./с. Эффективность 9/16 = 56%. Вот это да! Налицо оптимизация скомпилированой программы — результирующее быстродействие выходит на уровне ассемблера! Программа честно сравнивает полученный результат с 55252 шт. «счастливых» билетов, наверняка компилятор просто «развернул» внутренний цикл.
Теперь пора протестировать эффективность интерпретаторов для Raspberry Pi на русском микрокомпьютере МВ77.07. У меня есть целая коллекция: — php, ruby, perl, python 2 и python 3. Поскольку, микрокомпьютером управляет многозадачная операционная система, а приоритет скриптовых интерпретаторов средний, то на результаты может повлиять запуск системных процессов во время теста. Поэтому, для чистоты эксперимента тесты будут запускаться по пять раз, а полученные значения усредняться.
Начну с интерпретатора веб-скриптов php5-fpm 5.6.38:

=1; $a--) {
for ($b=10; $b>=1; $b--) {
$g=$a+$b;
for ($c=10; $c>=1; $c--) {
$h=$g+$c;
for ($d=10; $d>=1; $d--) {
for ($e=10; $e>=1; $e--) {
$i=$d+$e;
for ($f=10; $f>=1; $f--) {
if ($h==($i+$f)) $j++;
}
}
}
}
}
}
$k=microtime_float()-$k;
echo "Счастливых билетов: $j шт.";
echo "<Ьr>";
echo "Время расчёта: $k с.";
echo "<Ьr>";
?>


Результат — 1,9 с, загрузка процессора — 98%, быстродействие — 3,7 млн.оп./с, а эффективность — 0,9%! Вот это номер! Меньше процента! С другой стороны, от интерпретатора веб-скриптов вряд ли стоит ожидать многого.
Теперь, ruby 2.1.15:
a=0
b=0
c=0
d=0
e=0
f=0
g=0
h=0
i=0
j=0
k=Time.new
for a in 0..9
for b in 0..9
g=a+b
for c in 0..9
h=g+c
for d in 0..9
for e in 0..9
i=d+e
for f in 0..9
if h==i+f
j=j+1
end
end
end
end
end
end
end
k=Time.new-k
puts j
puts k


Усреднённый результат — 5 с, загрузка процессора — 50%, быстродействие 1,4 млн.оп./с, а эффективность — 0,35%. Странно, но почему-то ruby использует только половину процессорного времени… А вот результаты не ахти.
Далее, набортный perl 5.20.2:
use Time::HiRes qw(time);

$a=0;
$b=0;
$c=0;
$d=0;
$e=0;
$f=0;
$g=0;
$h=0;
$i=0;
$j=0;
$k=Time::HiRes::time;
for ($a=10; $a>=1; $a--) {

for ($b=10; $b>=1; $b--) {

$g=$a+$b;

for ($c=10; $c>=1; $c--) {

$h=$g+$c;

for ($d=10; $d>=1; $d--) {

for ($e=10; $e>=1; $e--) {

$i=$d+$e;

for ($f=10; $f>=1; $f--) {

if ($h==($i+$f)) {
$j++;

}

}

}

}

}

}

}
$k=Time::HiRes::time-$k;
print($j);
print"\n";
print($k);
print"\n";


Усреднённый результат — 9,4 с, загрузка процессора — 98%, быстродействие 0,74 млн.оп./с, эффективность — 0,18%. Perl использует процессорное время по максимуму, а результат ещё хуже, чем у ruby.
Наконец, для выполнения теста интерпретаторами python 2.7 и python 3.4.2 будет использоваться один и тот же скрипт:
a=0
b=0
c=0
d=0
e=0
f=0
g=0
h=0
i=0
j=0
from datetime import datetime
k=datetime.now()
for a in range(10):
for b in range(10):
g=a+b
for c in range(10):
h=g+c
for d in range(10):
for e in range(10):
i=d+e
for f in range(10):
if h==i+f:
j=j+1
k=datetime.now()-k
print (j)
print (k)

Python 2 почти не отличился от perl:

Усреднённый результат — 9,4с, загрузка процессора — 98%, быстродействие 0,74 млн.оп./с, эффективность — 0,18%.
А что же обновлённый python 3.4.2?

Усреднённый результат — 15,2с, загрузка процессора — 98%, быстродействие 0,46 млн.оп./с, эффективность — 0,114%. Что-то уж совсем python3 не блещет, сделаем ему поблажку, возьмём за результат лучший — 14с, тогда быстродействие выходит ровно 0,5 млн.оп./с, а эффективность интерпретатора чуть лучше — 0,1235%. Это значит, что интерпретатор выполняет программу в 800 раз медленнее, чем может сам микропроцессор!
Мне могут возразить, что тест испытывался на процессоре, разработанным в России, и наверняка в нём присутствуют какие-либо подводные камни, как в первом мультклеточном процессоре? Отнюдь, прогон теста Виталием Самуровым (https://pmk.arbinada.com/ru/comment/11595#comment-11595) на RaspberryPi Zero с разогнанным до 1ГГц ARM11 процессоре BCM2835 показал сходные результаты:
pi@raspberrypi:~/PythonStuff $ python tickets.py
55252
0:00:05.366339
pi@raspberrypi:~/PythonStuff $ python3 tickets.py
55252
0:00:05.084681

Python 2 показал результат — 5.3 с, быстродействие 1,3 млн.оп./с, эффективность — 0,13%.
Python 3 показал почти такой же результат — 5,1 с, быстродействие 1,4 млн.оп./с, эффективность — 0,14%.
Я был просто изумлён результатами — я никак не ожидал что интерпретатор веб-скриптов — php5-fpm показал наилучшее быстродействие из всех имеющихся у меня интерпретаторов, а Arduino UNO вышла победителем! Raspberry Pi даже при подсчёте «счастливых» на php за 0,9 секунд не дотягивает до Arduino! Только разогнанный до 1ГГц RaspberryPi Zero может при выполнении тестового php-скрипта победить оптимизирующий компилятор 16МГц микроконтроллера Arduino. А что же официально рекомендуемый для Raspberry Pi python 3? Увы, но с ним даже 1ГГц RaspberryPi Zero в шесть раз медленнее старенькой 16МГц Arduino.
Что в итоге? Апгрейд с Arduino до Raspberry Pi в самом лучшем случае не даёт выигрыша, а на деле получается шаг назад. Вроде бы приобретён микрокомпьютер в полста раз мощнее микроконтроллера старенькой отладочной платы, а на деле счастливый обладатель новинки благодаря ленивым программистам интерпретаторов делает шаг назад! Неужто, один маленький шаг назад одного человека выливается в гигантский прыжок назад для всего человечества? Python 3 на глазах превращает вычислительную мощь процессора в вычислительные мощи интерпретатора — российская 90-нм СБИС К1879ХБ1Я, выпускающаяся с 2011 года,

скатывается до советской 6-мкм БИС К580ИК80 образца 1977 года,

это просто огромный, в 34 года, шаг назад! Даже если учесть примерно десятилетнее отставание новейших российских микропроцессоров от мирового уровня — всё равно, неэффективный программный код отбрасывает пользователя устройства на десяток-другой лет назад! А что, если скомпилировать тест с помощью gcc? Увы, компилятор gcc в комплект программного обеспечения Raspberry Pi не входит и его придётся устанавливать отдельно.
Для завершения нашего исследования, скомпилируем силами gcc вот эту программу:
#include
#include
char a;
char b;
char c;
char d;
char e;
char f;
char g;
char h;
char i;
unsigned short j;
float k;
float l;
int main() {
k=clock();
j=0;
for (a=10; a>=1; a--) {
for (b=10; b>=1; b--) {
g=a+b;
for (c=10; c>=1; c--) {
h=g+c;
for (d=10; d>=1; d--) {
for (e=10; e>=1; e--) {
i=d+e;
for (f=10; f>=1; f--) {
if (h==(f+i)) j++;
}
}
}
}
}
}
k=clock()-k;
l=k/CLOCKS_PER_SEC;
printf("%d",j);
printf("\n");
printf("%f",l,"\n");
printf("\n");
return 0;
}

Поскольку, gcc имеет широкие возможности по оптимизации программного кода, будем компилировать и запускать тест на всех уровнях оптимизации — по умолчанию, О0, О1, О2 и О3:

GCC выдал результаты на значительно превосходящие ничтожные потуги рекомендуемых к применению на Raspberry Pi компиляторов! Вдобавок, результаты расчётов оказались весьма стабильны и не имели такого явного разброса, как на ruby, perl и python 2/3.
Итак, результат оптимизации по умолчанию — 0,174 с, быстродействие — 40 млн.оп./с, эффективность — 10%. Что-то не очень, хотя уже в десять раз быстрее php и в 81 раз быстрее python 3.
Результат с выключенной оптимизации О0 тот же — 0,174 с, быстродействие — 40 млн.оп./с, эффективность — 10%. Очевидно, в gcc по умолчанию оптимизация отключена.
Результат с начальной оптимизацией О1 в семь раз лучше — 0,024 с, быстродействие — 290 млн.оп./с, эффективность — 72%. Вот это уже похоже на правду, вот она мощь процессора!
Результат со средней оптимизацией О2 тот же — 0,02 с, быстродействие — 350 млн.оп./с, эффективность — 86%. Можно сказать, что результат приближается к ассемблерному. За 14% проигрыш по производительности программе, написанной в машинных кодах с тщательнейшей оптимизацией программист платит значительно меньшей трудоёмкостью программирования. Теперь над задачей оптимизации работает микропроцессор, а не ломает голову человек.
Наконец, максимальное ускорение! Результат с наибольшей оптимизацией О3 поражает — 0,014 с, быстродействие — 500 млн.оп./с, эффективность — 123%. Чудес не бывает, а вечный двигатель построить ещё никому не удалось. Компилятор для дальнейшей оптимизации просто развернул внутренний цикл в линейный участок, процессор теперь не выполняет почти миллион команд переходов, и в этом кроется секрет повышения производительности, показавшего результат, формально превышающий возможности процессора! Расплата — увеличение размера кода программы почти вдвое. Так, что здесь можно только выбирать из двух зол меньшее — производительность или размер файла.
Да, с помощью gcc можно получить полный доступ к процессорной мощи Raspberry Pi! Но компилятор имеет много параметров, и запуск компиляции может оказаться труднодоступным для начинающего любителя. Выложивший денег за новинку, любитель получает в красивой упаковке устройство, с настолько урезанными возможностями, что на деле оказывается хуже имеющегося старого. Да, с помощью новинки можно управлять умным домом даже через полмира — но вряд ли удастся выжать из Raspberry Pi стандартными средствами большего, чем из старой Arduino.
А в чём же заключается шаг назад при приобретении Raspberry Pi вместо Arduino UNO, ведь есть прогрессивный компилятор gcc, позволяющий отбросить ограничения нечистого на руку производителя и получить все вычислительные возможности процессора в своё полное и безраздельное пользование? Есть интересный проект для Arduino UNO от энтузиастов — прошивка GRBL, позволяющая превратить Arduino UNO в настоящий контроллер для станка с числовым программным управлением! Проект GRBL был разработан Сайменом Свале Скогсрудом и увидел свет в 2009 году, через четыре года после появления Arduino. Контроллер GRBL мог только выполнять команды ЧПУ с по СОМ-порту, но мог выполнять линейные и круговые интерполяции, но для управления им достаточного было самого простого компьютера! Да, в промышленном станке контроллер ЧПУ на Arduino мог вызвать только насмешливую улыбку, а вот в самодельном настольном фрезерном с достоинством фрезеровал и сверлил печатные платы, а также, небольшие фигурные детали из алюминия или пластика. Неудивительно, что проект GRBL приобрёл широкую популярность и не теряет актуальности и поныне. А как же Raspberry Pi, увидевший свет в 2012? Через четыре года в 2016, да и поныне нет простой и понятной программы, позволяющей превратить Raspberry Pi в контроллер ЧПУ станка. Есть проект LinuxCNC, который для нормальной работы в многозадачной ОС требует доработанного ядра. Даже система ЧПУ Mach3 вполне нормально работает под управлением Windows NT, благодаря специальному драйверу ядра. Но, не смотря на целый зоопарк появившихся дистрибутивов ОС для Raspberry Pi, я так и не нашёл дистрибутива под LinuxCNC. Увы, сияющие вершины светлого будущего массовой компьютеризации любительских самоделок на деле обернулись мрачным настоящим торговой экономики — зачем делать эффективное устройство, которое будет конкурировать с будущими моделями? В итоге, вместо Arduino, позволяющего построить простенький станок с числовым программным управлением, сейчас предлагается Raspberry Pi, представляющего собой больше развлекательное устройство, нередко после первых экспериментов, отправляющегося на дальнюю полку валяться без дела.