Восстановление после сбоя динамического выделения памяти

StackOverflow https://stackoverflow.com/questions/367310

Вопрос

Я работаю над встроенным процессором (400 МГц Intel PXA255 XScale), и мне показалось, что я видел один случай, когда не хватало памяти для выполнения "новой" операции.Программа не завершилась сбоем, поэтому я предположил, что другие потоки освободили свою память, и это было просто временное явление.Это довольно критичный код, поэтому выход невозможен, и удаленному пользователю необходимо вернуть какую-то ошибку.

Будет ли следующего небольшого исправления достаточно для решения проблемы, или есть способ получше?Прежде чем заменять каждое "новое" следующим кодом, я подумал, что хотел бы спросить.

char someArr[];
do{ 
    someArr = new char[10]; 
    Sleep(100); // no justification for choosing 100 ms
} while ( someArr == NULL );

Помогает ли вам сон?Должен ли я установить некоторое максимальное количество попыток?Можно ли везде использовать статическую инициализацию?

ОКОНЧАТЕЛЬНОЕ ОБНОВЛЕНИЕ: Большое вам спасибо за полезные ответы, но, оказывается, в коде, проверяющем неудачное выделение памяти, произошла ошибка.Я буду иметь в виду все эти ответы и заменю как можно больше malloc и new (особенно в коде обработки ошибок).

Это было полезно?

Решение

Существует несколько различных способов решения этой проблемы - обратите внимание, что инструкции по инструменту будут немного отличаться в зависимости от того, какую версию Windows CE / Windows Mobile вы используете.

Нужно ответить на несколько вопросов:

1.В вашем приложении происходит утечка памяти, что приводит к такому дефициту памяти?

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

1 и 2 можно проверить с помощью инструмента Windows CE AppVerifier, который может предоставить подробные средства ведения журнала памяти для вашего продукта.Другие инструменты для упаковки кучи также могут предоставлять аналогичную информацию (и могут быть более производительными), в зависимости от дизайна вашего продукта.

http://msdn.microsoft.com/en-us/library/aa446904.aspx

3.Часто ли вы выделяете и освобождаете память в этом процессе?

Windows CE, до версии ОС 6.0 (не путайте с Windows Mobile 6.x), имела ограничение виртуальной памяти 32 МБ на процесс, что, как правило, вызывает множество забавных проблем с фрагментацией.В этом случае, даже если у вас достаточно свободной физической памяти, возможно, у вас заканчивается виртуальная память.Использование пользовательских распределителей блоков обычно является средством решения этой проблемы.

4.Вы выделяете очень большие блоки памяти?(> 2 МБ)

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

5.Используете ли вы большое количество библиотек DLL?

Также связанные с 3, в зависимости от версии ОС, библиотеки DLL также могут очень быстро уменьшить общее количество доступных виртуальных машин.

Дальнейшие точки отсчета:

Обзор инструментов памяти CE

http://blogs.msdn.com/ce_base/archive/2006/01/11/511883.aspx

Инструмент управления целевым окном "mi"

http://msdn.microsoft.com/en-us/library/aa450013.aspx

Другие советы

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

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

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

  • Каждый процесс осознает, что память может быть исчерпана и может переход к состоянию, в котором он потребляет минимальный объем памяти.Часто эта стратегия реализуется путем создания исключения нехватки памяти.Исключение обрабатывается в main() или рядом с ним, и событие нехватки памяти по существу перезапускает программу с нуля.Этот режим отработки отказа может сработать, если объем памяти увеличивается в ответ на запросы пользователя;если требования программы к памяти растут независимо от того, что делает пользователь, это может привести к избиению.

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

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

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

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

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

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

Вы также можете попробовать sleep(0), который просто передаст управление любому другому потоку, готовому к запуску.Это позволит вашему потоку немедленно восстановить контроль, если все остальные потоки перейдут в спящий режим, вместо того, чтобы ждать его 100-миллисекундного предложения.Но если хотя бы один поток все еще хочет запуститься, вам все равно придется подождать, пока он не откажется от управления.В последний раз, когда я проверял, это обычно занимает 10 миллисекунд на машинах Linux.Я не знаю о других платформах.Ваш поток также может иметь более низкий приоритет в планировщике, если он добровольно перешел в спящий режим.

Основываясь на вашем вопросе, я предполагаю, что ваша куча распределена между несколькими потоками.

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

Если куча является общей, то описанное выше, вероятно, сработало бы.Однако, если у вас есть общая куча, то вызов "new", вероятно, приведет либо к блокировке вращения (аналогичному циклу, который у вас есть, но с использованием инструкций CAS), либо к блокировке на основе некоторых ресурсов ядра.

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

Я бы подумал о переопределении операторов "создать" и "удалить".Когда new терпит неудачу, вы можете заблокировать (или выполнить блокировку какой-либо переменной counter), ожидая, пока другой поток освободит память, а затем delete может либо сигнализировать о заблокированном "новом" потоке, либо увеличить переменную counter с помощью CAS.

Это должно обеспечить вам лучшую пропускную способность и быть немного более эффективным

Несколько моментов:

  • Встроенные программы часто выделяют всю память при запуске или используют только статическую память, чтобы избежать подобных ситуаций.
  • Если на устройстве не запущено что-то еще, что регулярно освобождает память, ваше решение вряд ли будет эффективным.
  • У Viper, который у меня есть, 64 МБ оперативной памяти, я не думаю, что у них меньше 32 МБ, сколько памяти использует ваше приложение?

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

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

Вы используете C ++.Таким образом, вы можете воспользоваться некоторыми утилитами C ++, чтобы облегчить себе жизнь.Например, почему бы не использовать new_handler ?

void my_new_handler() {
    // make room for memory, then return, or throw bad_alloc if
    // nothing can be freed.
}

int main() {
    std::set_new_handler(&my_new_handler);

    // every allocation done will ask my_new_handler if there is
    // no memory for use anymore. This answer tells you what the
    // standard allocator function does: 
    // https://stackoverflow.com/questions/377178
}

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

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

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

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

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

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

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

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

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

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

По-моему, здесь чем-то не так пахнет.Хммм...

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top