Динамическое восстановление памяти после сбоя - PullRequest
6 голосов
/ 15 декабря 2008

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

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

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

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

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

Ответы [ 9 ]

15 голосов
/ 15 декабря 2008

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

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

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

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

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

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

2 голосов
/ 15 декабря 2008

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

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

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

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

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

1 голос
/ 29 декабря 2008

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

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

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

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

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

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

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

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

1 голос
/ 28 декабря 2008

Существует несколько различных способов атаковать - обратите внимание, что инструкции инструмента будут немного отличаться в зависимости от используемой версии 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

1 голос
/ 28 декабря 2008

Вы используете 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 , если вам нужно знать объем памяти, необходимый для new_handler. Смотрите мой другой ответ о том, как это сделать. Таким образом, у вас есть одно центральное место для решения проблем с памятью вместо многих мест, связанных с этим.

1 голос
/ 17 декабря 2008

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

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

1 голос
/ 15 декабря 2008

Несколько баллов:

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

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

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

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

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

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

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

0 голосов
/ 15 декабря 2008

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

Для меня что-то здесь не пахнет. Ммм ...

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

...