Подтвердить что ты не робот

Оптимизация пространства вместо скорости в С++

Когда вы говорите "оптимизация", люди склонны думать "скорость". Но как насчет встроенных систем, где скорость не так важна, но память является основным ограничением? Каковы некоторые рекомендации, методы и трюки, которые можно использовать для извлечения лишних килобайт в ПЗУ и ОЗУ? Как один код "профиля", чтобы увидеть, где раздувается память?

P.S. Можно утверждать, что "преждевременно" оптимизация пространства во встроенных системах - это не все зло, потому что вы оставляете себе больше места для хранения данных и ползучести функций. Он также позволяет сократить расходы на производство оборудования, потому что ваш код может работать на меньшем ROM/RAM.

P.P.S. Ссылки на статьи и книги также приветствуются!

P.P.P.S. Эти вопросы тесно связаны: 404615, 1561629

4b9b3361

Ответ 1

Мой опыт из чрезвычайно ограниченной среды встроенной памяти:

  • Использовать буферы фиксированного размера. Не используйте указатели или динамическое распределение, потому что у них слишком много накладных расходов.
  • Используйте наименьший тип данных int, который работает.
  • Никогда не используйте рекурсию. Всегда используйте цикл.
  • Не пропускайте множество параметров функции. Вместо этого используйте глобальные переменные.:)

Ответ 2

Есть много вещей, которые вы можете сделать, чтобы уменьшить следы памяти, я уверен, что люди написали книги по этому предмету, но некоторые из них:

  • Параметры компилятора для уменьшения размера кода (включая опции -Os и параметры упаковки/выравнивания)

  • Параметры компоновщика для снятия мертвого кода

  • Если вы загружаете флэш (или ПЗУ) в плей-офф для выполнения (а не выполняете флэш-память), используйте сжатое флэш-изображение и распаковывайте его с помощью загрузчика.

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

  • Инструменты для поиска водяного знака стека (как правило, они заполняют стек шаблоном, выполняют программу, а затем видят, где находится шаблон), поэтому вы можете оптимально установить размер стека

  • И, конечно, оптимизация алгоритмов, используемых для хранения памяти (часто за счет скорости)

Ответ 3

Несколько очевидных

  • Если скорость не является критичной, выполните код непосредственно со вспышки.
  • Объявлять постоянные таблицы данных с помощью const. Это позволит избежать копирования данных из флеш-памяти в оперативную память.
  • Плотно упаковывайте таблицы больших данных с использованием самых маленьких типов данных и в правильном порядке, чтобы избежать заполнения.
  • Используйте сжатие для больших наборов данных (если код сжатия не перевешивает данные)
  • Отключите обработку исключений и RTTI.
  • Кто-нибудь упомянул об использовании -O?; -)

Складывание знаний в данные

Одно из правил философия Unix может помочь сделать код более компактным:

Правило представления: сворачивайте знания в данные, поэтому программная логика может быть глупой и надежной.

Не могу подсчитать, сколько раз я видел сложную логику ветвления, охватывающую многие страницы, которые можно было бы складывать в красивую компактную таблицу правил, констант и указателей функций. Государственные машины часто могут быть представлены таким образом (государственный шаблон). Также применяется шаблон команды. Это все о декларативных и императивных стилях программирования.

Коды журналов + двоичные данные вместо текста

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

Минимизировать количество потоков

Каждый поток нуждается в собственном блоке памяти для стека и TSS. Если вам не нужно предубеждение, подумайте о том, чтобы ваши задачи выполнялись совместно в рамках одного потока (совместная многозадачность).

Использовать пулы памяти вместо хранения

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

Динамическое распределение только при инициализации

Во встроенных системах, где работает только одно приложение, вы можете использовать динамическое распределение разумным образом, что не приводит к фрагментации: просто динамически выделяйте один раз в своих различных процедурах инициализации и никогда не освобождайте память. reserve() ваши контейнеры в правильной емкости и не позволяйте им автоматически расти. Если вам нужно часто выделять/освобождать буферы данных (например, для пакетов связи), то используйте пулы памяти. Однажды я даже увеличил время выполнения C/С++, чтобы прервать мою программу, если что-то попытается динамически распределить память после последовательности инициализации.

Ответ 4

Сгенерируйте файл карты из компоновщика. Он покажет, как распределяется память. Это хороший старт при оптимизации использования памяти. Он также покажет все функции и как выложено пространство кода.

Ответ 5

Как и при всей оптимизации, сначала оптимизируйте алгоритмы, затем оптимизируйте код и данные, наконец оптимизируйте компилятор.

Я не знаю, что делает ваша программа, поэтому я не могу советоваться с алгоритмами. Многие другие писали о компиляторе. Итак, вот несколько советов по коду и данным:

  • Устраните избыточность кода. Любой повторный код, который три или более строк длинный, повторяемый три раза в коде, должен быть изменен на вызов функции.
  • Устранить избыточность ваших данных. Найти наиболее компактное представление: объединить данные только для чтения и рассмотреть использование кодов сжатия.
  • Запустите код через обычный профайлер; исключить весь код, который не используется.

Ответ 7

Скомпилируйте в VS с помощью /Os. Часто это даже быстрее, чем оптимизация скорости, так как меньший размер кода == меньше пейджинга.

Сложение Comdat должно быть включено в компоновщике (по умолчанию это делается в версиях)

Будьте осторожны с упаковкой структуры данных; часто это приводит к тому, что компилятор генерирует больше кода (== больше памяти), чтобы сгенерировать сборку для доступа к неаудированной памяти. Использование 1 бит для булевого флага является классическим примером.

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

Ответ 8

Хорошо, что большинство из них уже упоминалось, но вот мой список:

  • Узнайте, что может сделать ваш компилятор. Прочитайте документацию по компилятору, поэкспериментируйте с примерами кода. Проверьте настройки.
  • Проверить сгенерированный код на уровне целевой оптимизации. Иногда результаты удивляют и часто оказывается, что оптимизация фактически замедляет работу (или просто занимает слишком много места).
  • выберите подходящую модель памяти. Если вы нацелитесь на действительно небольшую жесткую систему, большая или огромная модель памяти может оказаться не лучшим выбором (но, как правило, упрощает программирование для...)
  • Предпочитает статическое размещение. Использовать динамическое распределение только при запуске или более статический буфер (статический буфер с пулом или максимальным экземпляром).
  • Используйте типы данных C99. Используйте наименьший достаточный тип данных для типов хранения. Локальные переменные, такие как переменные цикла, иногда более эффективны с "быстрыми" типами данных.
  • Выберите встроенных кандидатов. Некоторые параметры тяжелой функции с относительно простыми органами лучше, когда они встроены. Или рассмотрим прохождение структуры параметров. Глобалы также являются опцией, но будьте осторожны - тесты и техническое обслуживание могут стать трудными, если кто-либо из них недостаточно разбирается.
  • Используйте ключевое слово const, помните о последствиях инициализации массива.
  • Файл карты, в идеале также с размерами модулей. Проверьте также, что включено в crt (действительно ли это необходимо?).
  • Рекурсия просто скажите нет (ограниченное пространство стека)
  • Числа с плавающей запятой - предпочитают математику с фиксированной точкой. Как правило, включает и вызывает много кода (даже для простого сложения или умножения).
  • С++, вы должны знать С++ ОЧЕНЬ ХОРОШО. Если вы этого не сделаете, запрограммируйте встроенные системы на C, пожалуйста. Те, кто осмеливаются, должны быть осторожны со всеми передовыми конструкциями С++ (наследование, шаблоны, исключения, перегрузка и т.д.). Подумайте о том, что ближе к HW-коду скорее Super-C и С++ используются там, где он рассчитывает: в логике высокого уровня, графическом интерфейсе и т.д.
  • Отключите все, что вам не нужно в настройках компилятора (будь то части библиотек, языковые конструкции и т.д.).

И последнее, но не менее важное - при поиске минимально возможного размера кода - не переусердствуйте. Остерегайтесь также производительности и ремонтопригодности. Сверх-оптимизированный код имеет тенденцию к быстрому распаду.

Ответ 9

Во-первых, сообщите своему компилятору о необходимости оптимизации размера кода. GCC имеет флаг -Os для этого.

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

Также обратите внимание на часто используемую упаковку структуры данных - если вы можете сбрить байт или два с них, вы можете существенно сократить объем использования памяти.

Ответ 10

Профилирующий код или раздувание данных можно сделать через файлы карт: для gcc см. здесь, для VS см. здесь.
Мне еще предстоит увидеть полезный инструмент для профилирования размеров (и у меня нет времени на исправление взлома VS AddIn).

Ответ 11

Если вы ищете хороший способ профилировать использование кучи вашего приложения, ознакомьтесь с инструментом valgrind massif. Это позволит вам делать снимки вашего профиля использования памяти приложения с течением времени, и затем вы можете использовать эту информацию, чтобы лучше видеть, где находятся "низкие висячие фрукты", и направлять свои оптимизации соответственно.

Ответ 12

сверху, что другие предлагают:

Ограничить использование функций С++, писать как в ANSI C с небольшими расширениями. Стандартные шаблоны (std::) используют большую систему динамического распределения. Если можно, вообще не используйте шаблоны. Хотя они и не являются вредоносными, они делают слишком простой способ генерировать много и много машинного кода всего за пару простых, чистых, элегантных инструкций высокого уровня. Это поощряет письмо таким образом, что, несмотря на все преимущества "чистого кода", очень голоден.

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

Совместите свои структуры вручную или используйте #pragma pack

{char a; long b; char c; long d; char e; char f; } //is 18 bytes, 
{char a; char c; char d; char f; long b; long d; } //is 12 bytes.

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

Интеллектуальное балансирование использования malloc()/новых и статических структур.

Если вам нужна часть функциональности данной библиотеки, подумайте о том, чтобы написать свой собственный.

Разверните короткие петли.

for(i=0;i<3;i++){ transform_vector[i]; }

больше, чем

transform_vector[0];
transform_vector[1];
transform_vector[2];

Не делайте этого для более длинных.

Паковать несколько файлов вместе, чтобы компилировать короткие короткие функции компилятора и выполнять различные оптимизации. Linker не может.

Ответ 13

Не бойтесь писать "маленькие языки" внутри вашей программы. Иногда таблица строк и интерпретатор могут получить LOT. Например, в системе, над которой я работал, у нас есть много внутренних таблиц, к которым нужно обращаться по-разному (зацикливание, что угодно). У нас есть внутренняя система команд для ссылки на таблицы, которые образуют своего рода язык на полпути, который довольно компактен для того, что получает дон.

Но, БУДЬТЕ ОСТОРОЖНЫ! Знайте, что вы пишете такие вещи (я написал один случайно, я сам) и ДОКУМЕНТ, что вы делаете. Первоначальные разработчики, похоже, не осознавали, что они делают, поэтому гораздо сложнее управлять, чем должно быть.

Ответ 14

Оптимизация - популярный термин, но часто технически неверный. Это буквально означает сделать оптимальным. Такое состояние никогда не достигается ни по скорости, ни по размеру. Мы можем просто принять меры для перехода к оптимизации.

Многие (но не все) методы, используемые для перехода к минимальному времени к вычислительному результату, приносят в жертву потребность в памяти, и многие (но не все) методы, используемые для перехода к минимальному требованию к памяти, увеличивают время до результата.

Сокращение потребностей в памяти составляет фиксированное количество общих методов. Трудно найти конкретную технику, которая не аккуратно вписывается в одну или несколько из них. Если вы сделали все из них, у вас было бы что-то очень близкое к минимальному требованию к пространству для программы, если бы не абсолютный минимум. Для реального приложения для этого может потребоваться команда опытных программистов за тысячу лет.

  • Удалите всю избыточность из хранимых данных, включая промежуточные.
  • Удалите все необходимые для хранения данных, которые могут быть потоковыми.
  • Выделите только количество необходимых байтов, а не больше.
  • Удалите все неиспользуемые данные.
  • Удалите все неиспользуемые переменные.
  • Бесплатные данные, как только это больше не возможно.
  • Удалите все неиспользуемые алгоритмы и ветки внутри алгоритмов.
  • Найдите алгоритм, который представлен в исполнительном блоке минимального размера.
  • Удалите все неиспользуемое пространство между элементами.

Это компьютерный взгляд на тему, а не на разработчика.

Например, упаковка структуры данных - это усилие, которое объединяет (3) и (9) выше. Сжатие данных - это способ, по крайней мере, частично достичь (1) выше. Сокращение накладных расходов на конструкциях программирования более высокого уровня является способом достижения определенного прогресса в (7) и (8). Динамическое распределение - это попытка использовать многозадачную среду для использования (3). Предупреждения компиляции, если они включены, могут помочь с (5). Деструкторы пытаются помочь (6). Сокеты, потоки и трубы могут использоваться для выполнения (2). Упрощение полинома - это метод получения основы в (8).

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

Использование опции -Os в компиляторе gnu делает запрос к компилятору, чтобы попытаться найти шаблоны, которые можно преобразовать для их выполнения, но -O - это флаг агрегата, который включает в себя ряд функций оптимизации, каждый из которых который пытается выполнить преобразования для выполнения одной из 9 задач выше.

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

Ответ 15

Учитывайте стоимость реализации некоторых функций С++, таких как таблицы виртуальных функций и перегруженные операторы, которые создают временные объекты.

Ответ 16

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

Также следите за исключениями. С gcc я не верю, что для каждого блока try-catch возрастает размер (за исключением 2 функции call для каждого try-catch), но есть функция фиксированного размера, которая должна быть связана, в которой может быть расточительствование драгоценные байты