Каковы некоторые рекомендации по сокращению использования памяти в C? - PullRequest
38 голосов
/ 01 января 2009

Каковы некоторые рекомендации для "Memory Efficient C программирования". Главным образом для встроенных / мобильных устройств, каковы должны быть рекомендации для низкого потребления памяти?

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

Ответы [ 15 ]

27 голосов
/ 01 января 2009

В C, на гораздо более простом уровне, рассмотрим следующее:

  • Используйте #pragma pack (1) для выравнивания байтов ваших структур
  • Использование объединений, в которых структура может содержать различные типы данных
  • Используйте битовые поля вместо целых для хранения флагов и маленьких целых чисел
  • Избегайте использования массивов символов фиксированной длины для хранения строк, реализации пула строк и использования указателей.
  • Где хранятся ссылки на список перечисленных строк, например, имя шрифта, сохранить индекс в списке, а не строку
  • При использовании динамического выделения памяти заранее рассчитайте количество элементов, необходимое для избежания перераспределения.
20 голосов
/ 01 января 2009

Несколько предложений, которые я нашел полезными при работе со встроенными системами:

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

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

  • Чтобы уменьшить размер кода, проверьте настройки оптимизации вашего компилятора. Большинство компиляторов имеют переключатели для оптимизации скорости или размера кода. Может быть стоит поэкспериментировать с этими параметрами, чтобы увидеть, можно ли уменьшить размер скомпилированного кода. И, очевидно, по возможности устраняйте дублирующийся код.

  • Проверьте, сколько стековой памяти требуется вашей системе, и соответственно измените распределение памяти компоновщика (см. Ответы на этот вопрос ). Чтобы уменьшить использование стека, избегайте размещения больших структур данных в стеке (для любого значения «large», которое вам подходит).

16 голосов
/ 01 января 2009

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

9 голосов
/ 28 января 2009

Все хорошие рекомендации. Вот некоторые подходы к проектированию, которые я нашел полезными.

  • Байт-кодирование

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

  • Генерация кода

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

  • Будьте ненавистником данных

Будьте готовы тратить много циклов, если это позволит вам хранить абсолютно минимальную структуру данных. Обычно вы обнаружите, что производительность страдает очень мало.

8 голосов
/ 01 января 2009

Избегайте фрагментации памяти, используя свой собственный распределитель памяти (или осторожно используя системный распределитель).

Одним из методов является использование «распределителя плит» (см., Например, статью ) и несколько пулов памяти для объектов разных размеров.

8 голосов
/ 01 января 2009

Скорее всего, вам нужно будет тщательно выбирать свои алгоритмы. Цель для алгоритмов, которые имеют O (1) или O (log n) использование памяти (то есть, низкое). Например, непрерывные массивы с изменяемым размером (например, std::vector) в большинстве случаев требуют меньше памяти, чем связанные списки.

Иногда использование справочных таблиц может быть более выгодным как для размера кода , так и скорости. Если вам нужно только 64 записи в LUT, это 16 * 4 байта для sin / cos / tan (используйте симметрию!) По сравнению с большой функцией sin / cos / tan.

Сжатие иногда помогает. Простые алгоритмы, такие как RLE, легко сжимать / распаковывать при последовательном чтении.

Если вы имеете дело с графикой или аудио, рассмотрите различные форматы. Графика в палитре или в битах * может быть хорошим компромиссом для качества, и палитры могут быть разделены между многими изображениями, что еще больше сокращает объем данных. Звук может быть уменьшен с 16-битного до 8-битного или даже 4-битного, а стерео может быть преобразовано в моно. Частота дискретизации может быть уменьшена с 44,1 кГц до 22 кГц или 11 кГц. Эти преобразования звука значительно уменьшают размер данных (и, к сожалению, качество) и являются тривиальными (за исключением повторной выборки, но для этого предназначено программное обеспечение аудио =]).

* Полагаю, вы могли бы сжать это. Битовая упаковка для графики обычно означает уменьшение количества бит на канал, чтобы каждый пиксель мог уместиться в два байта (например, RGB565 или ARGB155) или один (ARGB232 или RGB332) из ​​исходных трех или четырех (RGB888 или ARGB8888 соответственно).

7 голосов
/ 01 января 2009

Предварительное выделение всей памяти заранее (т. Е. Никаких вызовов malloc, за исключением инициализации при запуске), безусловно, полезно для детерминированного использования памяти. В противном случае, разные архитектуры предоставляют методы, чтобы помочь. Например, некоторые процессоры ARM предоставляют альтернативный набор команд (Thumb), который почти вдвое уменьшает размер кода, используя 16-битные инструкции вместо обычных 32-битных. Конечно, при этом жертвуют скоростью ...

6 голосов
/ 01 января 2009

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

Некоторые примеры этого:

  1. Анализировать пар NMEA с помощью конечного автомата, собирая только необходимые поля в гораздо более эффективную структуру.
  2. Разбор XML с использованием SAX вместо DOM.
5 голосов
/ 01 января 2009

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

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

Мой опыт показывает, что почти все существенные оптимизации при исправлении приложения большого размера происходят из-за небольшого количества изменений: уменьшение размеров кэша, удаление некоторых текстур (конечно, это функциональное изменение, требующее соглашения с заинтересованными сторонами, то есть собраний). (поэтому может оказаться неэффективным с точки зрения вашего времени), повторно сэмплируйте звук, уменьшите предварительный размер выделенных пользователем куч, найдите способы освободить ресурсы, которые используются только временно, и перезагрузите их при необходимости снова. Иногда вы можете найти структуру размером 64 байта, которая может быть уменьшена до 16, или что-то в этом роде, но это редко самый низко висящий фрукт. Однако, если вы знаете, какие самые большие списки и массивы в приложении, тогда вы знаете, какие структуры следует искать вначале.

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

В прошлом я потратил лот времени, беспокоясь о размере кода. Основные соображения (кроме: убедитесь, что вы измерили его во время сборки, чтобы вы могли видеть его изменение):

1) Узнайте, на какой код ссылаются и на что. Если вы обнаружите, что вся библиотека XML связана с вашим приложением просто для анализа двухэлементного файла конфигурации, рассмотрите возможность изменения формата файла конфигурации и / или написания своего собственного тривиального анализатора. Если вы можете, используйте исходный или бинарный анализ, чтобы нарисовать большой граф зависимостей, и ищите большие компоненты только с небольшим числом пользователей: может быть возможно вычеркнуть их только с незначительными переписываниями кода. Будьте готовы играть дипломатом: если два разных компонента в вашем приложении используют XML, и вы хотите сократить его, то вам нужно убедить двух людей в преимуществах ручного запуска чего-то, что в настоящее время является надежной готовой библиотекой. .

2) Возиться с опциями компилятора. Обратитесь к документации для вашей платформы. Например, вы можете уменьшить допустимое увеличение размера кода по умолчанию из-за встраивания, и, по крайней мере, в GCC вы можете указать компилятору только применять оптимизацию, которая обычно не увеличивает размер кода.

3) По возможности используйте библиотеки уже на целевой платформе (ах), даже если это означает написание слоя адаптера. В приведенном выше примере XML вы можете обнаружить, что на вашей целевой платформе в любом случае всегда есть в памяти библиотека XML, потому что ОС использует ее, и в этом случае ссылка на нее динамическая.

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

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

3 голосов
/ 18 мая 2009
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...