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

Каковы некоторые рекомендации по сокращению использования памяти в C?

Какова лучшая практика для программирования с памятью C? В основном для встроенного/мобильного устройства, какими должны быть рекомендации по снижению потребления памяти?

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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

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

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

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

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

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

Ответ 5

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

Один из способов - использовать "распределитель slab" (см. статья) и несколько пулов памяти для объектов разного размера.

Ответ 6

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

Ответ 7

Скорее всего, вам нужно будет тщательно выбирать свои алгоритмы. Цель алгоритмов с использованием памяти 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 кГц. Эти звуковые преобразования значительно уменьшают размер их данных (и, к сожалению, качество) и тривиальны (за исключением повторной дискретизации, но то, что для аудиопрограмм для =]).

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

Ответ 8

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

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

  • Разберите пар NMEA с конечным автоматом, собирая только нужные поля в гораздо более эффективную структуру.
  • Разбирайте XML, используя SAX вместо DOM.

Ответ 9

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

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

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

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

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

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

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

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

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

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

Ответ 11

  • Уменьшить длину и устранить как можно больше строковых констант, чтобы уменьшить количество кодов

  • Внимательно рассмотрите компромиссы алгоритмов и таблиц поиска, где необходимо

  • Имейте в виду, как распределяются различные типы переменных.

    • Константы, вероятно, находятся в кодовом пространстве.
    • Статические переменные, вероятно, находятся в фиксированных ячейках памяти. Избегайте их, если это возможно.
    • Параметры, вероятно, хранятся в стеке или в регистрах.
    • Локальные переменные также могут быть выделены из стека. Не объявляйте большие локальные массивы или строки, если в условиях наихудшего случая код может закончиться из пространства стека.
    • У вас может не быть кучи - не может быть ОС для управления кучей для вас. Это приемлемо? Вам нужна функция malloc()?

Ответ 12

Один трюк, полезный в приложениях, - создать дневной фонд памяти. Выделите один блок при запуске, который достаточно велик, чтобы этого было достаточно для задач очистки. Если malloc/new потерпит неудачу, отпустите фонд дождливого дня и опубликуйте сообщение, позволяющее пользователю узнать, что ресурсы жесткие, и они должны сэкономить и в ближайшее время. Это был метод, используемый во многих приложениях Mac около 1990 года.

Ответ 13

Отличный способ ограничения требований к памяти - это максимально полагаться на libc или другие стандартные библиотеки, которые могут быть связаны динамически. Каждая дополнительная DLL или общий объект, который вы должны включить в свой проект, представляет собой значительную часть памяти, которую вы можете избежать.

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

Ответ 14

У меня есть презентация конференции Embedded Systems Conference по этой теме. Это с 2001 года, но все же оно очень применимо. См. документ.

Кроме того, если вы можете выбрать архитектуру целевого устройства, перейдя с чем-то вроде современной ARM с Thumb V2 или PowerPC с VLE или MIPS с MIPS16 или выбрав известные компактные цели, такие как Infineon TriCore или SH семья - очень хороший вариант. Не говоря уже о семействе NEC V850E, который красиво компактен. Или перейдите к AVR, который имеет отличную компактность кода (но это 8-разрядная машина). Все, кроме 32-битного RISC с фиксированной длиной, является хорошим выбором!

Ответ 15

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

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

  • Сгладьте дерево вызовов, чтобы уменьшить количество переменных в стеке в любой момент времени.
  • Преобразование больших локальных переменных в глобальные (уменьшает количество используемого стека, но увеличивает объем используемой глобальной памяти). Переменные можно объявить:

    • Глобальная область: видимая для всей программы
    • Static в области файлов: видимый в том же файле
    • Статическая область видимости функции: видимая внутри функции
    • ПРИМЕЧАНИЕ. Независимо от того, если изменения тезисов сделаны, вы должны быть осторожны с проблемами reentrant если у вас есть preemptive.

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

PS: Бонус для правильного использования?