• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

минимизация исходников

nikolz

Well-known member
Добрый день,Всем
---------------
Есть такая проблема.
Собрал на компе проект на СИ программа TTS модификация flite.
------------------------
Работает замечательно.
Быстрее реального времени в 380 раз.
--------------------
Теперь вопрос в портирование на ESP.
В проекте много файлов и много функций, но они все не требуются.
--------------------
Хотелось бы в автомате вытащить из исходников лишь те функции,
которые необходимы для исполнения моего main.
-------------------
вопрос к знатокам.
---------------
Есть ли готовый софт, который из исходников собирает библиотеку функций,
которые требуются для выполнения моей конкретной программы.
-----------------
Из объектной библиотеки вытащить не реально( или реально?),
так как в каждом исходном файле куча функций и не все нужные.
Нужен совет. Спасибо.
 

rst

Member
Хотелось бы в автомате вытащить из исходников лишь те функции,
которые необходимы для исполнения моего main.
Эту работу выполняет компоновщик. В исполняемый образ он компонует только функции/константы/переменные используемые в программе. Неиспользуемые не попадают в исполняемый образ.
 

nikolz

Well-known member
Эту работу выполняет компоновщик. В исполняемый образ он компонует только функции/константы/переменные используемые в программе. Неиспользуемые не попадают в исполняемый образ.
немного не так получается в некоторых случаях. Поправьте если ошибаюсь.
Пример 1:
Я собираю dll для луа и в нем есть функция, которая исполняет мой алгоритм.
DLL собирается из множества файлов, каждый файл содержит множество функций.
Полагаю что в этом случае в DLL компоновщик включит вообще все.
----------------------
пример 2:
Собираю свой проект на СИ и в 10 файлах есть 100 функций. Собирает проект main+ все 10 файлов.
Полагаю, что тоже включатся все функции.
----------------------
Возможно что если сначала все 10 файлов собрать в библиотеку то потом при сборке включатся лишь вызываемые.
===============
Но кроме тупой сборки, я хотел бы взять лишь нужные мне исходники и оптимизировать их.
А для этого нужно то, что написал выше.
 

aZholtikov

Active member
немного не так получается в некоторых случаях. Поправьте если ошибаюсь.
Пример 1:
Я собираю dll для луа и в нем есть функция, которая исполняет мой алгоритм.
DLL собирается из множества файлов, каждый файл содержит множество функций.
Полагаю что в этом случае в DLL компоновщик включит вообще все.
----------------------
пример 2:
Собираю свой проект на СИ и в 10 файлах есть 100 функций. Собирает проект main+ все 10 файлов.
Полагаю, что тоже включатся все функции.
----------------------
Возможно что если сначала все 10 файлов собрать в библиотеку то потом при сборке включатся лишь вызываемые.
===============
Но кроме тупой сборки, я хотел бы взять лишь нужные мне исходники и оптимизировать их.
А для этого нужно то, что написал выше.
Возможно я ошибаюсь, но...

Пишу программы в VSCode + PlatformIO на C++. Сейчас ради интереса озадачился этой темой и проверил размер одной своей программки - 372723.
Библиотека для теста - #include "Ethernet.h".
Ради интереса добавил еще одну ненужную функцию - Ethernet.hardwareStatus();. Программа стала 372771.

Значит компилятор ее не включает если она не используется.
 

pvvx

Активный участник сообщества
Значит компилятор ее не включает если она не используется.
Компилятор включает всё, что не попадя.
Пример - Если у вас switch(x) и в библиотеке (или вашем исходнике) описаны тысячи вариантов ветвлений функций от x, то все коды и включит. И не важно, что вы используете вызов только с x=1.
 

pvvx

Активный участник сообщества
А это и есть доказательство:
... проверил размер одной своей программки - 372723.
Ради интереса добавил еще одну ненужную функцию - Ethernet.hardwareStatus();. Программа стала 372771.
А полная либа уже включена. Но в ней у вас используется не более 10% кода.
 

pvvx

Активный участник сообщества
В проекте много файлов и много функций, но они все не требуются.
--------------------
Хотелось бы в автомате вытащить из исходников лишь те функции,
которые необходимы для исполнения моего main.
ИИ ещё не достиг такого.
Only ручная работа по переписыванию всего на свой код со своим алгоритмом. Других, более оптимальных, вариантов нет.
Типичные либы для того-же ESP содержат не менее 90% ненужного CPU кода, созданного для ардуино-писателей. Обычно содержат сотни проверок и стыковок с другими ненужными вам кусками кода...
 

rst

Member
DLL собирается из множества файлов, каждый файл содержит множество функций.
Полагаю что в этом случае в DLL компоновщик включит вообще все.
Нет. Как уже сказал выше - как правило только те переменные/функции, которые используются где-то в программе.
Правда для этого нужно, чтобы компилятор оформлял каждую функцию/переменную в выходном .obj-файле в отдельную секцию. Но современные си-компиляторы вроде как все так и делают по умолчанию (в некоторых только видел ключ, управляющий этим поведением - "выделять ли каждую функцию в отдельную секцию или нет"). Единственно что можно - это специально (если захотеть) на ассемблере написать несколько функций в одной секции. Так как в асм-файлах сам программист определяет границы секций/сегментов. Такие функции попадут в выходной образ целиком (если хотя бы одна функция из секции используется где-то в программе). Так как компоновщик оперирует секциям из .obj-файлов (входные секции/сегменты) из которых он компонует выходные секции/сегменты. И не может делить входные секции на части.

Это элементарно можно проверить: Пишете любую си-функцию, ставите где-нить в main() программы её вызов, компилите, сохраняете .map-файл. Далее - удаляете вызов функции из main() (саму функцию не убираете из проекта), компилите, сравниваете .map-файлы - видите, что во 2-м случае размер исполняемого образа значительно уменьшился (на величину выкинутой функции). Хотя компилятор её также компилит, в .obj-файлах она есть, но в .map-файле - отсутствует.

Собираю свой проект на СИ и в 10 файлах есть 100 функций. Собирает проект main+ все 10 файлов.
Полагаю, что тоже включатся все функции.
Собираю проект (си). В процессе сборки компоновщик подключает стандартную библиотеку (для данного CPU, для данной модели памяти и пр.). Ту, где находятся всякие memcpy(), printf() и пр. Библиотеки эти лежат в соответствующей папке компилятора (LIB). Любой файл из библиотеки для этого ядра весит ~1МБ. Библиотеки содержат уже скомпилированный код/данные. А выходной образ программы после компоновщика получается = несколько десятков КБ всего.
Подумайте почему так получается. А библиотеки - это те же самые объектные файлы.
 

rst

Member
Компилятор включает всё, что не попадя.
Пример - Если у вас switch(x) и в библиотеке (или вашем исходнике) описаны тысячи вариантов ветвлений функций от x, то все коды и включит. И не важно, что вы используете вызов только с x=1.
Разговор не про то, что включает компилятор, а про то, что включает компоновщик. А он, из массива секций с выхода компилятора, выберет только те, к которым есть обращение в программе.
А switch - вообще не причём. Компоновщик оперирует на уровне секций/сегментов. Которые формируются из отдельных функций. Вот на уровне функий и отбрасывается ненужные. То же самое и с данными.
 

pvvx

Активный участник сообщества
Любой файл из библиотеки для этого ядра весит ~1МБ. Библиотеки содержат уже скомпилированный код/данные. А выходной образ программы после компоновщика получается = несколько десятков КБ всего.
Подумайте почему так получается. А библиотеки - это те же самые объектные файлы.
Кода там не более 10% - остальное разметка для линковки и отладки.
 

pvvx

Активный участник сообщества
Разговор не про то, что включает компилятор, а про то, что включает компоновщик. А он, из массива секций с выхода компилятора, выберет только те, к которым есть обращение в программе.
А switch - вообще не причём. Компоновщик оперирует на уровне секций/сегментов. Которые формируются из отдельных функций. Вот на уровне функий и отбрасывается ненужные. То же самое и с данными.
С какими функциями и данными?
Напишите printf("") и будет включена пол мат-либы с данными и все варианты обработки возможных операторов в printf(), да file().
И из всего этого кода будет выполняться не более 0.5%.
 

pvvx

Активный участник сообщества
Разговор не про то, что включает компилятор, а про то, что включает компоновщик. А он, из массива секций с выхода компилятора, выберет только те, к которым есть обращение в программе.
А switch - вообще не причём. Компоновщик оперирует на уровне секций/сегментов. Которые формируются из отдельных функций. Вот на уровне функий и отбрасывается ненужные. То же самое и с данными.
Примерный аналог по логике распределения кода-функций для MCU:
C:
// какие-то регистры MCU
(секция 1) volatile int reg1;
(секция 2)  volatile int reg2;
(секция N) volatile int regN;
// данные
int data1 = 1;
int data2 = 2;
...
int dataN = N;
// функции
int func1() { reg1 = data1; return reg1;}
int func2() { reg2 = data2; return reg2;}
...
int funcN() { reg2 = dataN; return regN;}

// основная функция
int test(int x) {
    int ret = 0;
    if(x & 1) ret = func1();
    else if(x & 2) ret = func2();
    ...
    else if(x & N) ret = funcN();
    return ret; 
}

// main
int main(int a) {
    return test(2);
}
Каждая функция в своей секции, но всё равно ваш компоновщик ничего не отбросит.
 

pvvx

Активный участник сообщества
И где тогда “оптимизация” хотя-бы по размеру включаемого и никогда не исполняемого кода, если все либы и функции используют глубоко вложенную структуру с их общими внутренними переплетенными функциями, а внешняя вызываема вами функция состоит из пару операндов с вызовом этой паутины из переплетенных громадной вложенностью функций?

Такую структуру вложенности любят все “ардуинор-программеры”, как разложение на общие примитивы и проверки всяких детских соплей. В итоге компилятор и сборщик ничего не оптимизирует и уровень его оптимизации обычно составляет всего несколько процентов.
Отбросит всё если убрать вызов test() из:
return test(2);
И что останется? Пустой main()? Кому такое нужно?
Если функция, в которой этот printf() написан, не будет вызываться нигде в программе, то не будет включено ни байта из этой мат-либы.
Будет. В MCU и прочих операционных системах вызываются скрытые для вас функции для инициализации. Они и подцепят части из стандартных либ :p
 

enjoynering

Well-known member
Считаю что проверка "детских соплей" необходима, чтоб разработчик не выстрелил себе в ногу. А их отключение рано или поздно приведёт к печальным последствиям. Причём как показывает опыт стреляю все и новички и профи. И только pvvx никогда не делает ошибок. Ходят слухи, что он вообще не человек, а ИИ.
 
  • Like
Реакции: A_D

pvvx

Активный участник сообщества
Да, безусловно, @enjoynering не обойтись без подтирки соплей. На то, к примеру, есть давно разобранный пример у ESP8266 – функция перевода чипа в deep-sleep. Напомню – она включала 100 ms таймер, пыталась закрыть все соединения в Lwip(), послать через WiFi завершение соединения, и следовательно отрабатывала только через 100+ ms, но в самом конце валилась на ей-же отключенной Flash, возвращаясь из IRAM кода по ret в область Flash и вызывая ошибку исполнения инструкций CPU (исполняя шум на пустой шине) и иногда успевала в лететь в обработчик ошибок и записать в EEPROM что выход произведен по ошибке, а не по команде deep-sleep (это не всё, там ещё поболее наворочено). Следовательно при последующем старте, выходе из deep-sleep в коде великие ESP программеры понаказавкали в инициализации десятки проверок-гаданий причины старта - толи это мальчик был, толи кролик...

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

enjoynering

Well-known member
Ну что вы, только учусь. А в остальном вы правы - следить и учитывать надо по возможности все. Иначе может произойти беда.
 

rst

Member
Будет. В MCU и прочих операционных системах вызываются скрытые для вас функции для инициализации. Они и подцепят части из стандартных либ :p
И что? А какая связь между "частями из стандартных либ" и "printf()"?

PS: Буквально сутки назад отлаживал программу в ОЗУ STM8L. А ОЗУ там всего == 2кБ! И всё влезло в эти 2кБ и ещё место осталось.
Я понимаю, что для вас это - фантастика! :p
 
Сверху Снизу