60 Гц NSTimer и автоматически выпущенная память - PullRequest
6 голосов
/ 14 июля 2011

У меня NSTimer стрельба со скоростью 60 кадров в секунду.Он обновляет модель C ++, а затем рисует через Quartz 2D.Это работает хорошо, за исключением того, что память быстро накапливается, хотя я ничего не выделяю.Приборы не сообщают об утечках, но многие CFRunLoopTimers (я думаю, из повторяющихся NSTimer?), Похоже, накапливаются.Щелчок по окну или нажатие клавиши удаляет большинство из них, что, по-видимому, указывает на то, что пул авто-выпусков не достаточно часто сливается.Нужно ли полагаться на события для циклического пула (-ов) автоматического выпуска или есть лучший способ очистить память?

Любая помощь приветствуется, спасибо

-Sam

Создание таймера (timer - это ивар):

timer = [NSTimer scheduledTimerWithTimeInterval:1.0f / 60 target:self selector:@selector(update:) userInfo:nil repeats:YES];

update: метод:

- (void)update:(NSTimer *)timer {
    controller->Update();
    [self.view setNeedsDisplay:YES];
}

Обновление:

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

1.) [self.view setNeedsDisplay:YES] кажется виновником в порождении этих CFRunLoopTimers.Замена на [self.view display] устраняет проблему, но за счет производительности.

2.) Снижение частоты до 20-30 кадров в секунду и сохранение `[self.view setNeedsDisplay: YES] 'также вызываетпроблема исчезнет.

Это может означать, что setNeedsDisplay: не нравится, когда его часто называют (может быть, больше времени в секунду, чем может отображаться?).Я, честно говоря, не могу понять, в чем проблема с «перезвоном», если все, что он делает, это говорит, что представление отображается повторно в конце цикла событий.

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

Ответы [ 3 ]

6 голосов
/ 15 июля 2011

Обычно правильным решением было бы создание вложенного NSAutoreleasePool вокруг кода, насыщенного объектами.

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

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

CADisplayLink *frameLink = [CADisplayLink displayLinkWithTarget:self
                                                        selector:@selector(update:)];

// Notify the application at the refresh rate of the display (60 Hz)
frameLink.frameInterval = 1;

[frameLink addToRunLoop:[NSRunLoop mainRunLoop]
                forMode:NSDefaultRunLoopMode];

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

1 голос
/ 26 июля 2011

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

Документация для NSTimer гласит: «Из-за различных входных источников, которыми управляет типичный цикл выполнения, эффективное разрешение временного интервала для таймера ограничено порядка 50-100 миллисекунд».

1/60 - интервал прибл. 16,6 миллисекунды, так что вы намного выше эффективного разрешения NSTimer.

Ваше последующее замечание указывает, что снижение его частоты до 20-30 кадров в секунду исправляет это ... 20 кадров в секунду приводит к интервалу до 50 мс - в пределах документированного разрешения.

Документация также указывает, что это ничего не должно сломать ... однако я столкнулся с некоторыми странными ситуациями, когда инструменты вызывали проблемы с памятью, которых раньше не было. Получаете ли вы проблемы с памятью / предупреждения при запуске приложения в сборке выпуска без Xcode или присоединенных инструментов?

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

0 голосов
/ 25 июля 2011

Как уже предложено Kemenaran, я также думаю, что вы должны попробовать и использовать CADisplayLink объект.Еще одна причина заключается в том, что NSTimer имеет нижний предел срабатывания 50-100 миллисекунд ( источник ):

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

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

  1. , когда вы выполняете [self.view setNeedsDisplay:YES];, фреймворк планирует перерисовку представленияозначает CFRunLoopTimer;это объясняет, почему создается так много:

  2. , когда срабатывает CFRunLoopTimer, представление перерисовывается, флаг needsDisplay сбрасывается;

  3. в вашем случае, когда частота update высока, происходит то, что вы звоните setNeedsDisplay чаще, чем может произойти обновление;Итак, для каждого фактического обновления у вас есть несколько вызовов setNeedsDisplay, и также создаются несколько CFRunLoopTimer;

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

Для пункта 4: я думаю, что наиболее вероятное объяснениепервое: вы создаете очередь из CFRunLoopTimer s с частотой, намного превышающей ту, на которой вы можете ее «потреблять».Я говорю, что перерисовка занимает больше времени, чем update цикл, потому что вы говорите, что при вызове [view display] производительность снижается.

Если это правильно, то проблема будет сохраняться и с CADisplayLink (потому что это связано ск вызову обновления с слишком высокой частотой по сравнению со скоростью перерисовки), и единственным решением было бы найти другой способ сделать перерисовку (то есть, не используя setNeedsDisplay:YES)

На самом деле, я проверил источники cocos2dи setNeedsDisplay:YES почти не используется.Перерисовка (cocos2d обеспечивает частоту кадров 60 кадров в секунду) выполняется путем рисования непосредственно в буфер OpenGL, и я подозреваю, что это критическая точка для достижения этой частоты кадров.Вы также можете проверить, можете ли вы заменить слой вида на CAEAGLLayer (это должно быть довольно просто) и посмотреть, можете ли вы рисовать прямо на glBuffer.

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

...