Удивительное рядом - оптимизация кода компилятором и класс HardwareSerial

b707
Онлайн
Зарегистрирован: 26.05.2017

Возьмем пустой скетч. Откомпилируем:

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}
Скетч использует 444 байт (1%) памяти устройства. 
Глобальные переменные используют 9 байт (0%) динамической памяти,

Сразу непонятно, куда девались 9 байт, если в скетче нет переменных. Пустячок, но неприятно.

Добавим некую функцию:

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

int b707(void) {
  int a,b,c;
  a++;
  b=c+a;
 }
Скетч использует 444 байт (1%) памяти устройства. 
Глобальные переменные используют 9 байт (0%) динамической памяти

Как видим, ни размер кода, но использование памяти не изменилось, что совершенно логично. Наша функция определена, но нигде в программе не используется - поэтому компилятор ее выкинул из конечного кода.

Добавим вывод:

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

int b707(void) {
  int a,b,c;
  a++;
  b=c+a;
  Serial.print(a);
 }
Скетч использует 1284 байт (3%) памяти устройства. 
Глобальные переменные используют 182 байт (8%) динамической памяти

Опа! Функция b707()  по прежнему в программе не используется - но класс Serial подгружается и отжирает свои 1к во флеше и 170 байт оперативы (8%) !!!

Более того, если код функции

int b707(void) {
  int a,b,c;
  a++;
  b=c+a;
  Serial.print(a);
 }

вырезать из скетча, сохранить во внешний файл c расширением.cpp и ПРОСТО ПОЛОЖИТЬ в одну папку со скетчем - даже не добавляя никаких #include - он все равно будет подгружен компилятором, попадет в бинарный код и отожрет столько же памяти, как в предыдущем случае.

Вопрос к гуру - почему вызовы Serial.print, даже расположенные в неиспользуемых функциях, не выпиливаются оптимизатором из кода? Относится ли это ко всем классам или класс Serial особенный?

Практические выводы:

1. Если в скетче не хватает памяти и в конечном коде вам не нужен дианостический вывод в монитор - физически удаляйте все вызовы класса Serial, в том числе из неиспользуемых функций. Подключайте и отключайте диагностический вывод при поиощи директив условной компиляции #ifdef

2. (в принципе, это известно - но нелишне напомнить) Будьте внимательны к тому, какие файлы лежат в каталоге программы. Ардуино IDE подключает при компиляции все .cpp файлы из каталога скетча не спрашивая.

b707
Онлайн
Зарегистрирован: 26.05.2017

Да, забыл указать - использовалась Ардуино IDE 1.6.12, Win7 x64

Интересно было бы проверить, как себя ведут в этой ситуации другие компиляторы

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Также ведут (ну там +/- по результатам оптимизации). В целом это правильное и документированное поведение.

b707
Онлайн
Зарегистрирован: 26.05.2017

ЕвгенийП пишет:

Также ведут (ну там +/- по результатам оптимизации). В целом это правильное и документированное поведение.

Правильное? Что же правильного в том. что класс, к которому нет обращений в коде - занимает место в памяти?

ssss
Offline
Зарегистрирован: 01.07.2016

Ну это как бы типа сродни обработчикам прерываний. Может ли компиль самостоятельно их выкинуть? На основании чего?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

b707 пишет:

Правильное? Что же правильного в том. что класс, к которому нет обращений в коде - занимает место в памяти?

А Вы исследуйте вопрос поглубже, а именно - посмотрите, что именно он не выбрасывает. Убедитесь, что все там правильно.

Только внимательнее относитесь к делу, а то некоторые Ваши оговорки просто пугают. Вот, Вы пишете

b707 пишет:

Как видим, ни размер кода, но использование памяти не изменилось, что совершенно логично. Наша функция определена, но нигде в программе не используется - поэтому компилятор ее выкинул из конечного кода.

Уверен, что это именно оговорка, т.к. Вы, разумеется, понимаете, что компилятор никак не мог выбросить функцию, которая не объявлена как static, т.к. компилятро имеет дело с одним файлом и ему просто неоткуда узнать, что эта функция не вызывается не из какого-то другого. Стало быть выбросил эту Вашу функцию не компилятор, а линкер.

Линкер же не знает многого из того, что знает компилятор. В общем, Вы аккуратно, внимательно отследите что именно он оставляет в памяти и поймёте, почему он не смог этого выбросить. Это само по себе интересное исследование.

 

nik182
Offline
Зарегистрирован: 04.05.2015

Какой такой пустой скеч? Вы ж на си пишете. Если от вас скрывают, то это не значит что нет функции main, а в ней нет функции init. Попробуйте скомпилировать скеч в котором объявлена одна пустая main.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

b707 пишет:

Практические выводы:

1. Если в скетче не хватает памяти и в конечном коде вам не нужен дианостический вывод в монитор - физически удаляйте все вызовы класса Serial, в том числе из неиспользуемых функций. Подключайте и отключайте диагностический вывод при поиощи директив условной компиляции #ifdef

ок. т.е, если "вам не нужен диагностический вывод в монитор", то процесс ненаписания вызовов класса Serial в настоящем можно приравнять к процессу физического удаления всех вызовов класса Serial в будущем.

ещё раз для понимания сути высоты глубины логического дна рекомендации:

пример: Блинк!

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the UNO, MEGA and ZERO 
  it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN is set to
  the correct LED pin independent of which board is used.
  If you want to know what pin the on-board LED is connected to on your Arduino model, check
  the Technical Specs of your board  at https://www.arduino.cc/en/Main/Products
  
  This example code is in the public domain.

  modified 8 May 2014
  by Scott Fitzgerald
  
  modified 2 Sep 2016
  by Arturo Guadalupi
  
  modified 8 Sep 2016
  by Colby Newman
*/


// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
} 
Скетч использует 766 байт (12%) памяти устройства. Всего доступно 6144 байт.
Глобальные переменные используют 9 байт (0%) динамической памяти, оставляя 1015 байт для локальных переменных. Максимум: 1024 байт.

как вы думаете, почему скетч испольует 9 байт оперативки?

ну, это же очевидно! авторы скетча следовали рекомендации №1 b707.

а, не потому, что вы подумали...

b707
Онлайн
Зарегистрирован: 26.05.2017

Клапауций 112 пишет:

ок. т.е, если "вам не нужен диагностический вывод в монитор", то процесс ненаписания вызовов класса Serial в настоящем можно приравнять к процессу физического удаления всех вызовов класса Serial в будущем.

Очень верно подмечено. Не одному ж тебе велосипеды изобретать :)

 

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

b707 пишет:

Очень верно подмечено. Не одному ж тебе велосипеды изобретать.

вообще то - это был сарказм.

колись, зачем пишешь в скетч неиспользуемые функции?

b707
Онлайн
Зарегистрирован: 26.05.2017

Клапауций 112 пишет:

вообще то - это был сарказм.

Клапа, я догадался :) А мой ответ - самоирония.

Клапауций 112 пишет:

колись, зачем пишешь в скетч неиспользуемые функции?

в скетч лишние функции и правда писать не стоит, но ведь с функциями из подключаемых библиотек все обстоит так же.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

b707 пишет:

но ведь с функциями из подключаемых библиотек все обстоит так же.

абсолютно так же - не пиши в библиотеки неиспользуемые функции.