Как избежать нехватки памяти в приложении с высоким использованием памяти? С/С++

Я написал конвертер, который берет xml-файлы openstreetmap и преобразует их в двоичный формат рендеринга времени выполнения, который обычно составляет около 10% от исходного размера. Размер исходного файла обычно составляет 3 ГБ и больше. Входные файлы не загружаются в память все сразу, а транслируются по мере сбора точек и полигонов, затем на них запускается bsp и файл выводится. В последнее время на больших файлах ему не хватает памяти, и он умирает (у того, о котором идет речь, 14 миллионов точек и 1 миллион полигонов). Обычно моя программа использует от 1 ГБ до 1,2 ГБ оперативной памяти, когда это происходит. Я пытался увеличить виртуальную память с 2 до 8 ГБ (в XP), но это изменение не дало результата. Кроме того, поскольку этот код с открытым исходным кодом, я хотел бы, чтобы он работал независимо от доступной оперативной памяти (хотя и медленнее), он работает на Windows, Linux и Mac.

Какие методы я могу использовать, чтобы избежать нехватки памяти? Обработка данных небольшими подмножествами, а затем объединение окончательных результатов? Используя мой собственный обработчик типа виртуальной памяти? Любые другие идеи?


person KPexEA    schedule 12.04.2009    source источник


Ответы (15)


Во-первых, в 32-битной системе вы всегда будете ограничены 4 ГБ памяти, независимо от настроек файла подкачки. (И из них вашему процессу в Windows будет доступно только 2 ГБ. В Linux у вас обычно будет доступно около 3 ГБ)

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

Во-вторых, может помочь выделение небольших блоков памяти за раз. Зачастую проще найти 4 блока свободной памяти по 256 МБ, чем один блок по 1 ГБ.

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

person jalf    schedule 12.04.2009
comment
Windows может иметь 3 ГБ виртуального пространства с параметром /LARGEADDRESSAWARE. - person Shay Erlichmen; 12.04.2009
comment
Флаг позволяет процессу использовать до 4 ГБ, если ОС может это предоставить. Обычно Windows по-прежнему настроена на выделение каждому процессу только 2 ГБ. Это тоже можно изменить, рискуя нестабильностью драйвера, чтобы дать вам 3 ГБ, да. С PAE вы можете получить еще больше. Но 64-битная, вероятно, лучше. - person jalf; 12.04.2009
comment
Имхо, третий вариант самый важный. Помимо предоставления контроля над памятью, он также позволяет выполнять параллельную обработку. - person xtofl; 12.04.2009
comment
RE - Jalf с PAE: Насколько мне известно, Windows XP на самом деле не поддерживает 4 дополнительных бита адреса, с которыми поставляется большинство аппаратных средств (см. PAE). Я получаю это со страницы PAE в Википедии, на которой есть ссылка на веб-страницу Microsoft. Может ли кто-нибудь подтвердить на своем опыте, что winXP игнорирует лишние биты адреса? - person Trevor Boyd Smith; 13.04.2009
comment
@Trevor: я не пробовал, но я уверен, что XP поддерживает это (хотя, конечно, вам нужно включить его вручную). Но имеет ли это значение? В наши дни переход на 64-разрядную версию, вероятно, является гораздо лучшим решением. - person jalf; 13.04.2009

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

Поскольку ваша программа переносима на Linux, я предлагаю запустить ее под Valgrind, чтобы убедиться.

person Douglas Leeder    schedule 12.04.2009
comment
Да, проверил на утечки, их нет. - person KPexEA; 12.04.2009

Похоже, вы уже применяете подход к обработке XML на основе SAX (загрузка XML как едешь, а не все сразу).

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

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

Если вы не можете разделить свой алгоритм, вам, вероятно, понадобится что-то вроде файлы с отображением памяти.

В худшем случае вы можете попробовать использовать что-то вроде VirtualAlloc если вы работаете в системе Windows. Если вы работаете в 32-разрядной системе, вы можете попробовать использовать что-то вроде Расширение физического адреса (PAE).

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

person Brian R. Bondy    schedule 12.04.2009

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

person dwc    schedule 12.04.2009

Предполагая, что вы используете Windows XP, если вы только что превысили лимит памяти и не хотите или не имеете времени переделывать код, как было предложено выше, вы можете добавить переключатель /3GB в свой boot.ini, а затем достаточно установить переключатель компоновщика, чтобы получить дополнительный 1 ГБ памяти.

person Stephen Nutt    schedule 12.04.2009
comment
Не так просто использовать 3 Гб. Вы должны убедиться, что все ваши арифметические операции с указателями безопасны, иначе вы выйдете из строя, когда использование памяти станет высоким. См. blogs.msdn.com/oldnewthing/archive/2004/08. /12/213468.aspx, чтобы узнать больше. - person eran; 13.04.2009

Вы должны понимать, что виртуальная память отличается от «ОЗУ» тем, что объем используемой вами виртуальной памяти — это общий объем, который вы зарезервировали, а реальная память (в Windows она называется рабочим набором) — это память, которую вы фактически изменены или заблокированы.

Как заметил кто-то еще, на 32-разрядных платформах Windows ограничение виртуальной памяти составляет 2 гигабайта, если вы не установите специальный флаг для 3 гигабайт и не можете гарантировать, что все указатели как в вашем коде, так и в любых библиотеках, которые вы используете, используют только беззнаковые указатели.

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

Я столкнулся с 32-битной стеной в Windows, но у меня нет опыта обхода этих ограничений в Linux, поэтому я говорил только о стороне Windows.

person Doug Boone    schedule 12.04.2009

В 32-разрядной версии XP максимальное адресное пространство программы составляет 2 ГБ. Тогда у вас есть фрагментация из-за загрузки DLL и драйверов в ваше адресное пространство. Наконец, у вас есть проблема с фрагментацией кучи.

Лучше всего покончить с этим и запустить как 64-битный процесс (в 64-битной системе). Внезапно все эти проблемы уходят. Вы можете использовать лучшую кучу, чтобы смягчить эффекты фрагментации кучи, и вы можете попробовать использовать VirtualAlloc, чтобы захватить вашу память в один большой непрерывный кусок (и тогда вы сможете управлять ею оттуда!), Чтобы препятствовать фрагментации DLL/драйверов.

Наконец, вы можете разделить BSP между процессами. Сложно и болезненно, и, честно говоря, было бы проще просто поместить его на диск, но теоретически вы могли бы повысить производительность, если бы у вас была группа процессов, обменивающихся информацией, если бы вы могли хранить все резидентно (и предполагая, что вы можете быть умнее памяти, чем ОС). может обрабатывать буферизацию файлов... что очень важно). Каждому процессу потребуется гораздо меньше памяти, и поэтому он не должен работать с ограничением адресного пространства в 2 ГБ. Конечно, вы будете сжигать ОЗУ / подкачку намного быстрее.

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

Боже, разве 64-битные вычисления не звучат намного лучше, чем другие варианты?

person Christopher Smith    schedule 12.04.2009

Как вы распределяете память для очков? Вы выделяете точку по одной (например, pt = new Point ). Затем, в зависимости от размера точки, часть памяти может быть потрачена впустую. Например, в Windows память выделяется кратно 16 байтам, поэтому даже если вы попросите попытаться выделить 1 байт, ОС фактически выделит 16 байтов.

В этом случае может помочь использование распределителя памяти. Вы можете сделать быструю проверку с помощью распределителя STL. (перегрузите новый оператор для класса Point и используйте распределитель STL для выделения памяти, а не «malloc» или новый оператор по умолчанию).

person Nitin Bhide    schedule 12.04.2009
comment
Я распределяю точки и полигоны с помощью диспетчера кучи, поэтому они занимают столько места, сколько необходимо, и почти не подслушивают, поскольку моя куча выделяет (в данном случае) фрагменты размером 1 МБ и распределяет запросы из каждого фрагмента. - person KPexEA; 13.04.2009
comment
Другой причиной может быть «фрагментация памяти» (т. е. память доступна небольшими порциями, но когда вы запрашиваете «1 МБ», непрерывный фрагмент размером 1 МБ недоступен. Использует ли синтаксический анализатор XML «менеджер кучи»? Может быть, синтаксический анализатор XML использует стандартное распределение памяти и вызывает фрагментацию? - person Nitin Bhide; 14.04.2009

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

Если вы не хотите тратить время на оптимизацию использования памяти, почему бы не попробовать консервативный мусор Коллекционер? Это замена плагина для malloc()/new и free(). На самом деле, free() не работает, поэтому вы можете просто удалить эти вызовы из своей программы. Если вместо этого вы вручную оптимизируете свою программу и управляете пулом памяти, как было предложено ранее, вы в конечном итоге будете выполнять большую часть работы, которую CGC уже делает за вас.

person Barry Brown    schedule 12.04.2009

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

person Community    schedule 12.04.2009

Звучит так, как будто вы выполняете преобразование txt в двоичный файл, так зачем вам хранить все данные в памяти?.
Разве вы не можете просто прочитать примитив из txt (xml), а затем сохранить в двоичный поток?

person Shay Erlichmen    schedule 12.04.2009

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

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

Теперь это звучит легко, не так ли? (Рад, что мне не нужно это делать :) )

person xtofl    schedule 12.04.2009

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

Вот несколько вещей, которые вы можете сделать, чтобы помочь в этой ситуации:

Наконец, позвольте мне указать, что сложные задачи требуют сложных мер. Если вы думаете, что можете позволить себе 64-битную машину с 8 ГБ ОЗУ, то просто используйте алгоритм «чтение файла в память, обработка данных, запись вывода», даже если на это уйдет день.

person Ash    schedule 13.04.2009

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

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

person mike    schedule 26.01.2011

Это старый вопрос, но, поскольку я недавно сделал то же самое....

Нет простого ответа. В идеальном мире вы бы использовали машину с огромным адресным пространством (т.е. 64 бита) и большим объемом физической памяти. Одного огромного адресного пространства недостаточно, иначе он просто зависнет. В этом случае разберите файл XML в базу данных и с соответствующими запросами извлеките то, что вам нужно. Вполне вероятно, что это то, что делает сам OSM (я полагаю, что мир составляет около 330 ГБ).

На самом деле я все еще использую XP 32bit из соображений целесообразности.

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

Единственный способ, который я нашел, чтобы заставить все это работать на небольшой площади на 32-битной машине, заключался в том, чтобы очень тщательно подумать о том, что я делаю и что нужно, когда нужно, и разбить задачу на куски. Эффективная память (никогда не использует более ~ 100 МБ), но не очень быстрая, но тогда это не имеет значения - как часто нужно анализировать данные XML?

person nmw01223    schedule 24.11.2014