Память продолжает увеличиваться при загрузке и выпуске NSImage - PullRequest
2 голосов
/ 19 февраля 2011

У меня проблема с тем, что мое приложение агрессивно потребляет память до «порочной точки» при последовательной загрузке файла изображения.Например, рассмотрим следующий код, который многократно загружает и выпускает 15-мегабайтный файл JPEG (большой размер файла для целей тестирования):

NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];  
for(int i=0; i<1000; i++) {  
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];  
    [image release];  
}

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

Что мне имеет смысл, если она выделит память, а затем система освободит ее так,что моя статистика "Real Mem" в Activity Monitor остается небольшой, а не в гигабайтах с последовательными итерациями загрузки / выпуска.На практике я, возможно, никогда не окажусь в этой точке, но мне кажется странным, что моя статистика «Real Mem» Activity Monitor в конечном итоге превосходит все другие приложения, когда фактическая резидентная память, требуемая в любое время, как правило, мала.Первоначально я думал, что это какая-то утечка памяти или проблема с кэшированием, но это, скорее, связано с агрессивным распределением памяти в приложении и отложенным освобождением памяти в системе (не то, чтобы что-то не так, если ОС имеет такую ​​политику - если это так.на самом деле так оно и работает).Возможно, мне чего-то не хватает в целом.

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

-

ПОСЛЕДУЮЩИЙ:

Баварский, спасибо.Обтекание NSAutoreleasePool разрешает итеративный рост памяти при загрузке того же файла:

NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];  
for(int i=0; i<1000; i++) {  
    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];  
    [image release];
    [apool drain];
}

Однако это не решает проблему, при которой объем памяти остается увеличенным после освобождения изображения (и истощения NSAutoreleasePool).Например, при загрузке моего 15-мегабайтного изображения JPEG память «Real Mem» переходит с установившегося режима на 8 мегабайт до примерно 25 мегабайт и остается там.(В моем приложении есть только кнопка Interface Builder, имеющая проводной IBAction для метода, который вызывает только тот цикл for, который я скопировал).Я ожидаю, что после завершения цикла for (или если будет загружено и выпущено только одно изображение), статистика Real Mem снизится до номинального использования памяти на уровне приложения.

Кажется разумным, что другие вещив фоновом режиме можно также загрузить при вызове функциональности NSImage, что может увеличить объем памяти.Однако изображения разных размеров (15 МБ, 30 МБ, 50 МБ и т. Д.) Пропорционально увеличивают объем памяти в приложении, что заставляет меня думать, что это больше, чем такое распределение.

Далее, если я попытаюсь загрузить отдельные изображения последовательно (скажем, 15MBjpeg-1.jpg, 15MBjpeg-2.jpg и т. д.) память иногда соединений для каждого нового загруженного изображения.Например, если два изображения загружаются последовательно, использование памяти «Real Mem» для приложения после загрузки / выпуска теперь составляет примерно 50 МБ, и оно никогда не уменьшается - по моим наблюдениям.Это поведение продолжается при загрузке последующих изображений, так что приложение может загружаться в сотни МБ памяти, используемой в режиме «Real Mem», после загрузки нескольких больших изображений.

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

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

-

СЛЕДУЮЩИЙ № 2

bbum, спасибо за отличную статью.Я запустил Распределение инструментов с моей тестовой программой и не обнаружил роста кучи.Как и в упомянутом сообщении в блоге, мой подход заключался в том, чтобы: 1) нажать кнопку «Загрузить и выпустить образ» на моем интерфейсе Interface Builder (который вызывает поведение загрузки / выпуска), 2) нажать «Пометить кучу» несколько раз каждые несколько секундв Распределении инструментов, а затем 3) повторите 1) и 2).

Используя этот подход, динамические снимки последовательно отображали 0 байт в столбце «Рост кучи» с течением времени (3 щелчка каждые 5 секунд в течение 15 секунд), что означаетчто нет дополнительной памяти, выделенной из базового Heapshot.Кроме того, в области статистики каждый раз, когда я нажимаю кнопку «Загрузить и выпускать изображение», появляется Malloc размером 13,25 МБ, но количество активных байтов равно 0 байтам, что означает, что он полностью освобожден.Если я три раза нажму кнопку «Загрузить и выпустить образ», общий размер байтов для изображения составит 39,75 МБ (3 * 13,25 МБ), что означает, что 39,75 МБ было выделено, но полностью освобождено, поскольку количество активных байтов равно 0. График распределениярезко возрастает и возвращается обратно, так как это довольно быстрая операция.Кажется, все это имеет смысл в том, что нет никакого утечки и никакого роста в использовании памяти в стационарном состоянии.

Но, что мне теперь делать, если моя статистика "Real Mem" все еще высока?Я знаю, что Activity Monitor не является стандартом для устранения проблем с памятью.Но «Real Mem» все еще остается на высоком уровне, пока я не закрою программу, а затем «Real Mem» возвращается в категорию «Free», что для меня странно.

Я тестировал этот же подход сдва изображения (15MBjpeg-1.jpg, 15MBjpeg-2.jpg), просто продублировав код одним и тем же методом, и я больше не наблюдаю роста кучи.Очевидно, что было выделено больше средств.Тем не менее, теперь «Real Mem» увеличивается примерно вдвое, как в случае загрузки и выпуска только одного изображения.И опять же, оно не уменьшается в устойчивом состоянии тестовой программы.

Могу ли я что-нибудь сделать дальше?Вот тестовый код для загрузки / выпуска одного изображения для всех, кто хочет попробовать его (просто подключите кнопку IB к openFiles):

#import "TestMemory.h" // only declares -(IBAction) openFiles:(id)sender;
@implementation TestMemory
-(IBAction) openFiles:(id)sender {
    NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];
    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];
    [image release];
    [apool drain];
}
@end

Спасибо за чтение.:)

-

FOLLOW-UP # 3

bbum, нет У меня не включена сборка мусора (для GC установлено значение Unsupportedв настройках проекта).Я исследовал память, используя vmmap и панель VM Tracker в Распределении инструментов.Я предположил, что вы имели в виду данные VM Tracker, когда говорили VM Instruments, поскольку они сообщают ту же информацию, что и vmmap.После открытия одного изображения с помощью моей кнопки «Загрузить и выпустить образ» некоторые важные номера памяти включают следующее (из VM Tracker):

Type %ofRes ResSize VirtSize Res% %AllDirty DirtySize<br/> __TEXT 38% 33.84MB 80.45MB 42% 0% 0Bytes<br/> <code>*Dirty* 32% 28,23 МБ 114,99 МБ 24% 100% 17.11MB MALLOC_LARGE 14% 13.25MB 13.25MB 100% 0% 4KB<br/> Carbon 11% 9.86MB 9.86MB 100% 20% 3.46MB<br> <code>VM_ALLOCATE 9% 8.43MB 48.17MB 18% 49% 8.43MB<br/> ...

Интересно, что при последующих загрузках / выпусках одного изображения "Resident Size" типов Dirty и VM_ALLOCATE увеличивается на~ .3MB, и «грязный размер» этих типов также увеличивается со временем.(VM_ALLOCATE представляется подмножеством Dirty ).Другие типы не изменяются при последующих загрузках / выпусках.

Я не уверен, что именно нужно забрать из этих данных или как я могу использовать их, чтобы программа освободила память.Кажется, что тип VM_ALLOCATE может быть порцией, которая не освобождается, но это только предположение.Возможно ли, что некоторая часть базовой реализации инициализации NSImage сохраняет кэш файла образа и не освобождает его?Опять же, как указывалось ранее, меня интригует, что каждая последующая загрузка / выпуск одного и того же файла изображения почти не потребляет ресурсы (ЦП, шлифовка диска и т. Д.) И время настенных часов по сравнению с первой загрузкой / выпуском.Заранее спасибо.

1 Ответ

2 голосов
/ 19 февраля 2011
  • Что сказал Баварий;Вы пытались окружить его NSAutoreleasePool.

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

  • Это главный кандидат для анализа снимков кучи .

(Спасибо за продолжение; полезно!)

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

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

  • Попробуйте взглянуть на инструмент VM или используйте vm_map в командной строке, чтобы взглянуть на то, что потребляетадресное пространство в вашем приложении.

...