Страницы

Поиск по вопросам

суббота, 28 декабря 2019 г.

Минимизация потребления памяти корректировкой типов

#c #оптимизация #память #типы_данных


Работаю с проектом, реализующим кодирование информации. В коде имеются достаточно
большие двумерные массивы. Вопрос в следующем: есть ли смысл минимизировать потребление
памяти, прицельно используя специальные типы данных?
Пример 1: int stateTable[64][6]; а там, в этой таблице, сплошь нули и единицы. Нужно
ли приводить к виду uint8_t stateTable[64][6];? При этом придётся перелопачивать весь
проект и править все типы во всех методах, где используется эта таблица.
Пример 2: for(int i; i < N; ++i) - а по логике работы больше итератор не может быть
больше 64. Нужно ли "забирать" у него лишнюю память?
Стоят ли подобные ухищрения времени, или это незначащие мелочи?
    


Ответы

Ответ 1



64*6*4 = 1536 - полтора килобайта. Я бы даже не переживал за такие размеры. Но если эту таблицу нужно постоянно копировать туда-сюда, а там на самом деле ноли и единицы, то может и упаковать ее (используя битовые операции) При этом придётся перелопачивать весь проект и править все типы во всех методах, где используется эта таблица. Для этого используют typedef. Заводят свой новый тип и его уже используют. Это позволит и посмотреть на реальное использование. Да и просто обычными ifdef можно будет легко переключать. for(int i; i < N; ++i) А вот здесь все куда интереснее. Вы можете конечно написать uint8_t (и не забыть инициализировать i, а то мало чего). Но вот только это не улучшит ситуацию. Компилятор скорее всего забросит i в регистр, где оно будет как минимум 32бита. А если использовать uint8_t, то компилятор может взять и начать делать касты (но может и не начать). Что бы не гадать, в stdint.h завезли специальные типы для этого случая. Вам наверно подойдет 'uint_fast8_t'. На моей машине внутри этот тип определен так typedef unsigned char uint_fast8_t; (я думал, там будет unsigned int). Но вот использовать этот тип как элемент массива я уже бы не стал - обычно все алгоритмы кодирования информации очень трепетно к размерам относятся, а здесь может от компилятора к компилятору меняться. Итого. Мое предложение - для всех "сомневающихся случаев" заведите свой тип, объявите его в одном глобальном хедере и просто используйте. Если что то поменяется - так будет проще.

Ответ 2



Я бы смотрел, что важнее - скорость или память. Битовые операции просто потребуют лишних вычислений. Байты - тоже не самые быстрые по скорости. int обычно быстрее (см., например, сравнение в этом ответе). Поэтому я бы перед окончательным принятием решения проводил эксперименты... А для облегчения их проведения - действительно, использовал бы typedef, как советует @KoVadim.

Ответ 3



Смысл минимизировать потребление памяти, прицельно используя специальные типы данных - есть и огромный. Но есть он только в массово инстанциируемых в памяти элементах данных. Ваш пример с int stateTable[64][6]; - пример как раз таки правильный, но такие фактические размеры массива слишком малы, чтобы говорить о необходимости экономии. (Однако это зависит и от конкретной платформы, конечно. На каких-нибудь embedded платформах с микроскопическим количеством памяти даже такая экономия может быть существенной.) Если у вас, например, есть структура, создаваемая в миллионах/миллиардах экземпляров, то следя за упаковкой и выравниванием, пользуясь экономными типами данных и битовыми полями, вы во многих случаях сможете в разы уменьшить потребление памяти программой. (Это, кстати, именно то, для чего в языке существуют битовые поля.) Об этом надо беспокоиться заранее и стараться избегать прямой зависимости пользовательских структур данных от явно прописанных типов данных, таких как int, чтобы потом не оказаться в ситуации "надо перелопачивать код всего проекта". Пользуйтесь промежуточными typedef-именами для типов и/или принципом DRY, чтобы избежать прописывания жестко прошитых явных типов в коде программы. Непродуманное прописывание явные типов в коде - это такая же ошибка, как прописывание в коде "магических констант". Разумеется здесь не надо впадать и в другую крайность, т.е. придумывать собственные имена всем типам без исключения. Как правило, ваш опыт и ваше чутье подскажут вам, какие типы в вашей программе являются потенциально "подвижными", т.е. каким типам надо заранее дать пользовательские typedef-имена. Что же касается "оптимизации" размеров локальных переменных, как в вашем примере с for - этого не просто бесполезно, а вредно. "Оптимизировать" единичные локальные переменные смысла нет. Наоборот, в таких ситуациях лучше всегда пользоваться "натуральными" типами - int/unsigned int/size_t для целых значений и double для плавающих, если у вас нет каких-то специальных веских причин использовать другой тип.

Комментариев нет:

Отправить комментарий