Улучшения поиска утечки памяти

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

Это сообщение оказалось довольно огромным. Гарантированы извинения за это, хотя я думаю в этом случае, объясняя детали максимально полностью. Явно так, потому что это дает Вам целое изображение всех вещей, которые я сделал для нахождения этого педераста, который был много. Одна только эта ошибка взяла меня примерно три 10 + дни часа для разыскивания...

Когда я ищу утечки

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

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

Фаза 1: Утечки говорят мне, что существует утечка

Leaks with 2 GeneralBlock-160 leaks at 160 bytes in Foundation's NSPushAutoreleasePool
(источник: enrogue.com)

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

Фаза 2: отслеживание стека

The stack trace for the leak
(источник: enrogue.com)

Оба из GeneralBlock-160 утечки имеют идентичные отслеживания стека (который нечетен, так как мне сгруппировали его "идентичные следы", но так или иначе), которые запускаются в thread_assign_default и конец в malloc под _NSAPDataCreate. Промежуточный, нет абсолютно ничего, что коррелирует к моему приложению. Ни один из тех вызовов не являются "моими". Таким образом, я делаю некоторый поиск с помощью Google вокруг для выяснения то, для чего они могли бы использоваться.

Сначала у нас есть много методов, которые, очевидно, имеют отношение к обратному вызову потока, такому как вызовы потока POSIX, входящие в вызовы NSThread.

В № 8-6 в этом (инвертированном) отслеживании стека мы имеем +[NSThread exit] сопровождаемый pthread_exit и _pthread_exit который интересен, но по моему опыту я не могу действительно сказать, показательно ли из некоторого конкретного случая или если это просто, "как дела идут".

После этого у нас есть названный метод очистки потока _pthread_tsd_cleanup - независимо от того, что "tsd" стоит, поскольку я не уверен, но независимо, я иду дальше.

В # 4-#3 мы имеем:

CA::Transaction::release_thread(void*)
CAPushAutoreleasePool

Интересный. Мы имеем Core Animation здесь. Это, я изучил очень твердый путь, средства, что я, вероятно, делаю UIKit вызовы от фонового потока, который я не должен. Большой вопрос состоит в том где, и как. В то время как может быть легко сказать, что "Вы не должны звонить UIKit от Вас olde фоновый поток", не столь легко знать то, что точно составляет как a UIKit звонить. Как Вы будете видеть в этом случае, это совсем не очевидно.

Затем № 2-1 оказывается слишком низким уровнем, чтобы иметь любое реальное применение. Я думаю.

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

Фаза 3: return в изобилии

Предложите, чтобы у нас было дерево вызова, которое выглядит примерно так:

App start
    |
Some init
  |      \
A init   B init - Other case - Fourth case
   \     /              \
 Some case            Third case
     |
  Fifth case
   ...

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

Таким образом, я имею

// ...
[fooClass doSomethingAwesome:withThisCoolThing];
// ...

И я делаю

// ...
return;
[fooClass doSomethingAwesome:withThisCoolThing];
// ...

И затем установите приложение на устройстве, закройте его, alt-tab к Инструментам, поразите CMDR, молоток на приложении как обезьяна, ищите утечки, и после возможно, 10 "циклов", если нет ничего, я прихожу к заключению, что утечка далее снижается на код. Возможно в fooClass doSomethingAwesome: или ниже вызова к fooClass.

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

Существует несколько проблем с этим методом.

  1. Утечки памяти имеют тенденцию быть немного снобистскими о том, когда показать себя. Вам нужны романтичная музыка и свечи, так сказать, и срезание одного конца в одном месте иногда приводит к решению утечки памяти не появиться вообще. Я часто должен был возвращаться, потому что утечка появилась после того, как я добавил, скажем, эту строку: UIImage *a; (который, очевидно, не протекает отдельно),
  2. Это мучительно медленно и утомительно, чтобы сделать для большой программы. Особенно, если Вы заканчиваете тем, что имели необходимость создать резервную копию снова.
  3. Трудно отслеживать. Я продолжал включать // 17 14.48.25: 3 leaks @ RSx10 который на английском языке имел в виду "17-го июля, 14:48.25: 3 утечки произошли, когда я неоднократно выбирал объект, 10 раз" опрыснутый всюду по целому приложению. Грязный, но по крайней мере это позволило мне видеть ясно, где я протестировал вещи и каковы результаты были.

Этот метод в конечном счете удалил меня к самой нижней части класса, который обработал миниатюры. Класс имел два метода, тот, который инициализировал вещи и затем сделал a [NSThread detachThreadWithSeparator:] звоните в отдельный метод, который обработал действительные образы и поместил их в отдельные представления после уменьшения масштаба их к правильному размеру.

Это был вид подобных это:

// no leaks if I return here
[NSThread detachNewThreadSelector:@selector(loadThumbnails) toTarget:self withObject:nil];
// leaks appear if I return here

Но если я вошел -loadThumbnails и пониженный через него, утечки исчезли бы и появились бы очень случайным способом. При одном обширном выполнении у меня были бы утечки и если я спустил оператор возврата ниже, например. UIImage *small, *bloated; У меня было бы появление утечек. Короче говоря, это было очень ошибочно.

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

Это - хорошая подсказка. Таким образом, я добавил a BOOL названный worldExists который был установлен на NO как только новая сессия инициировалась и затем начала опрыскивать -loadThumbnails for цикл с

if (worldExists) [action]
if (worldExists) [action 2]
// ...

и также удостоверился, что вышел из цикла, как только я узнал это !worldExists. Но утечка осталась.

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

Таким образом, я пытался добавить это в самом верху -loadThumbnails:

for (int i = 0; i < 50 && worldExists; i++) {
    [NSThread sleepForTimeInterval:0.1f];
}
return;

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

Наконец, я вставил точку останова -dealloc для класса миниатюры. Отслеживание стека для этого было похоже на это:

#0  -[Thumbs dealloc] (self=0x162ec0, _cmd=0x32299664) at /Users/me/Documents/myapp/Classes/Thumbs.m:28
#1  0x32c0571a in -[NSObject release] ()
#2  0x32b824d0 in __NSFinalizeThreadData ()
#3  0x30c3e598 in _pthread_tsd_cleanup ()
#4  0x30c3e2b2 in _pthread_exit ()
#5  0x30c3e216 in pthread_exit ()
#6  0x32b15ffe in +[NSThread exit] ()
#7  0x32b81d16 in __NSThread__main__ ()
#8  0x30c8f78c in _pthread_start ()
#9  0x30c85078 in thread_start ()

Хорошо... это не выглядит слишком плохо. Если я ожидаю до -loadThumbnails метод закончен, трассировка выглядит по-другому хотя:

#0  -[Thumbs dealloc] (self=0x194880, _cmd=0x32299664) at /Users/me/Documents/myapp/Classes/Thumbs.m:26
#1  0x32c0571a in -[NSObject release] ()
#2  0x00009556 in -[WorldLoader dealloc] (self=0x192ba0, _cmd=0x32299664) at /Users/me/Documents/myapp/Classes/WorldLoader.m:33
#3  0x32c0571a in -[NSObject release] ()
#4  0x000045b2 in -[WorldViewController setupWorldWithPath:] (self=0x11e9d0, _cmd=0x3fee0, path=0x4cb84) at /Users/me/Documents/myapp/Classes/WorldViewController.m:98
#5  0x32c29ffa in -[NSObject performSelector:withObject:] ()
#6  0x32b81ece in __NSThreadPerformPerform ()
#7  0x32c23c14 in CFRunLoopRunSpecific ()
#8  0x32c234e0 in CFRunLoopRunInMode ()
#9  0x30d620da in GSEventRunModal ()
#10 0x30d62186 in GSEventRun ()
#11 0x314d54c8 in -[UIApplication _run] ()
#12 0x314d39f2 in UIApplicationMain ()
#13 0x00002fd2 in main (argc=1, argv=0x2ffff5dc) at /Users/me/Documents/myapp/main.m:14

Очень отличающийся, на самом деле. На данном этапе я был все еще невежествен, хотите верьте, хотите нет, но я наконец выяснил то, что продолжалось.

Проблема следующая: когда я делаю [NSThread detachNewThreadSelector:] в загрузчике миниатюры, NSThread сохраняет объект, пока поток не заканчивается. В случае, где загрузка миниатюры не заканчивается, прежде чем я загружу другую сессию, весь мой сохраняет на загрузчике миниатюры, выпущены, но так как поток все еще работает, NSThread поддерживает его.

Как только поток возвращается из -loadThumbnails, NSThread выпуски это, это совершает нападки 0, сохраняют, и идет прямо в -dealloc... в то время как все еще в фоновом режиме распараллеливают.

И когда я затем звоню [super dealloc], UIView покорно попытки удалить себя из его суперпредставления, которое является a UIKit обратитесь к фоновому потоку. Следовательно, утечка происходит.

Решение, которое я предложил для решения этого, состояло в том, чтобы перенести загрузчик в два других метода. Я переименовал его к -_loadThumbnails и затем сделал следующее:

[self retain]; // <-- added this before the detaching
[NSThread detachNewThreadSelector:@selector(loadThumbnails) toTarget:self withObject:nil];

// added these two new methods
- (void)doneLoadingThumbnails
{
    [self release];
}
-(void)loadThumbnails
{
    [self _loadThumbnails];
    [self performSelectorOnMainThread:@selector(doneLoadingThumbnails) withObject:nil waitUntilDone:NO];
}

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

Какое обоснование я пропускал в вышеупомянутом процессе? В какой точке Вы понимали, где проблема была? Каковы были избыточные шаги в моем методе? Я могу пропустить фазу 3 (return в изобилии), так или иначе, или сокращенный это, или делают это более эффективным?

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

Какой процесс Вы используете, чтобы более эффективно разыскать его?

6
задан Glorfindel 2 August 2019 в 17:13
поделиться

1 ответ

Какие аргументы я пропустил в приведенном выше process?

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

2
ответ дан 17 December 2019 в 06:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: