iOS / Core-Animation: настройка производительности - PullRequest
16 голосов
/ 15 февраля 2011

У меня запущено приложение на iPad. но он работает очень плохо - у меня скорость ниже 15 кадров в секунду. Кто-нибудь может помочь мне оптимизировать?

Это в основном колесо (производное от UIView), содержащее 12 кнопок (производное от UIControl). image of control

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

Итак, мое колесо содержит:

- (void) displayLinkIsCallingBack: (CADisplayLink *) dispLink 
{
    :
    // using CATransaction like this goes from 14fps to 19fps
    [CATransaction begin];
    [CATransaction setDisableActions: YES];

    // NEG, as coord system is flipped/fucked
    self.transform = CGAffineTransformMakeRotation(-thetaWheel);

    [CATransaction commit];

    if (BLA)
        [self rotateNotch: direction];  
}

… который вычисляет из недавнего сенсорного ввода новое вращение колеса. Здесь уже есть одна проблема производительности, которую я преследую в отдельном потоке: Core Core-Animation для iOS: проблемы производительности с матрицами CATransaction / Interpolating transform

Эта процедура также проверяет, выполнило ли колесо еще один оборот на 1/12, и, если это так, дает указание всем 12 кнопкам изменить размер:

// Wheel.m
- (void) rotateNotch: (int) direction
{
    for (int i=0; i < [self buttonCount] ; i++)
    {
            CustomButton * b = (CustomButton *) [self.buttons objectAtIndex: i];

    // Note that b.btnSize is a dynamic property which will calculate
    // the desired button size based on the button index and the wheels rotation.
            [b resize: b.btnSize];
    }
}

Теперь для фактического кода изменения размера, в button.m:

// Button.m

- (void) scaleFrom: (float) s_old
                to: (float) s_new
              time: (float) t
{
    CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath: @"transform.scale"];

    [scaleAnimation setDuration:  t ];
    [scaleAnimation setFromValue:  (id) [NSNumber numberWithDouble: s_old] ];
    [scaleAnimation setToValue:  (id) [NSNumber numberWithDouble: s_new] ];
    [scaleAnimation setTimingFunction:  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut] ];
    [scaleAnimation setFillMode: kCAFillModeForwards];
    scaleAnimation.removedOnCompletion = NO;

    [self.contentsLayer addAnimation: scaleAnimation 
                              forKey: @"transform.scale"];

    if (self.displayShadow && self.shadowLayer)
        [self.shadowLayer addAnimation: scaleAnimation 
                                forKey: @"transform.scale"];    

    size = s_new;
}

// - - - 

- (void) resize: (float) newSize
{
    [self scaleFrom: size
                 to: newSize
               time: 1.];
}

Интересно, не связана ли эта проблема с накладными расходами нескольких операций transform.scale, стоящих в очереди - изменение размера каждой кнопки занимает целую секунду, а если я вращаю колесо быстро, я могу вращать пару оборотов в секунду ; это означает, что размер каждой кнопки изменяется 24 раза в секунду.

** создание слоя кнопки **

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

contentsLayer.setRasterize = YES;

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

Не могу поверить, что это облагает налогом устройство за его пределами. однако основной инструмент анимации говорит мне об обратном; пока я вращаю колесо (перетаскивая палец по кругу), он сообщает ~ 15 кадров в секунду.

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

Должно быть, я что-то не так делаю! а что?

РЕДАКТИРОВАТЬ: вот код, отвечающий за генерацию слоя контента кнопки (т.е. фигуры с тенью):

- (CALayer *) makeContentsLayer
{
    CAShapeLayer * shapeOutline = [CAShapeLayer layer];
    shapeOutline.path = self.pOutline;

    CALayer * contents = [CALayer layer];

    // get the smallest rectangle centred on (0,0) that completely contains the button
    CGRect R = CGRectIntegral(CGPathGetPathBoundingBox(self.pOutline)); 
    float xMax = MAX(abs(R.origin.x), abs(R.origin.x+R.size.width));
    float yMax = MAX(abs(R.origin.y), abs(R.origin.y+R.size.height));
    CGRect S = CGRectMake(-xMax, -yMax, 2*xMax, 2*yMax);
    contents.bounds = S; 

    contents.shouldRasterize = YES; // try NO also

    switch (technique)
    {
        case kMethodMask:
            // clip contents layer by outline (outline centered on (0, 0))
            contents.backgroundColor = self.clr; 
            contents.mask = shapeOutline;
            break;

        case kMethodComposite:
            shapeOutline.fillColor = self.clr;
            [contents addSublayer: shapeOutline];
            self.shapeLayer = shapeOutline;
            break;

        default:
            break;
    }

    if (NO)
        [self markPosition: CGPointZero
                   onLayer: contents ];

    //[self refreshTextLayer];

    //[contents addSublayer: self.shapeLayerForText];

    return contents;
}

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

** скомпрометировав дизайн пользовательского интерфейса для получения приемлемой частоты кадров **

РЕДАКТИРОВАТЬ: Теперь я попытался отключить поведение динамического изменения размера, пока колесо не установится в новую позицию, и установив wheel.setRasterize = YES. таким образом, он эффективно вращает один предварительно визуализированный UIView (который, по общему признанию, занимает большую часть экрана) под пальцем (что он с радостью делает при скорости ~ 60 кадров в секунду), пока колесо не остановится, в этот момент он выполняет эту медленную анимацию изменения размера ( @ <20fps). </p>

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

РЕДАКТИРОВАТЬ: я только что попытался в качестве эксперимента изменить размеры кнопок вручную; т.е. вставьте обратный вызов ссылки на дисплей в каждую кнопку и динамически рассчитайте ожидаемый размер этого данного кадра, явно отключите анимацию с помощью CATransaction так же, как я делал с колесом, установите новую матрицу преобразования (преобразование масштаба, сгенерированное из ожидаемого размера). В добавление к этому я установил слой содержимого кнопок shouldRasterize = YES. поэтому следует просто масштабировать 12 битовых карт каждого кадра на один UIView, который сам вращается. Удивительно, но это очень медленно, это даже останавливает симулятор. Это определенно в 10 раз медленнее, чем делать это автоматически с помощью анимации ядра.

Ответы [ 5 ]

15 голосов
/ 26 февраля 2011

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

Не угадай. Профиль.

Кажется, вы пытаетесь внести изменения без профилирования кода. Изменение некоторого подозрительного кода и скрещивание пальцев на самом деле не работает. Вы должны профилировать свой код, изучая, как long выполняется каждая задача и как often они должны выполняться. Попробуйте разбить свои задачи и поместите код профилирования в размеры time и frequency. Еще лучше, если вы можете измерить, сколько memory используется для каждой задачи или сколько других системных ресурсов. Найдите свое узкое место, основываясь на доказательствах, а не на своих чувствах.

Для вашей конкретной проблемы, вы думаете, что программа работает очень медленно, когда начинает работать ваша работа по изменению размера. Но вы уверены? Это может быть что-то еще. Мы не будем знать наверняка, пока не получим фактические данные профилирования.

Минимизируйте проблемную область и измерьте реальную производительность перед внесением изменений.

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

Для вашей конкретной проблемы, если изменение размера действительно является проблемой, попробуйте проверить, как она работает. Сколько времени занимает один вызов для изменения размера? Как часто вам нужно выполнять работу по изменению размера, чтобы завершить работу?

Улучшите это. Как? Это зависит.

Теперь у вас есть проблемный блок кода и его текущая производительность. Теперь вы должны улучшить его. Как? ну, это действительно зависит от того, в чем проблема. вы могли бы искать better algorithms, вы могли бы делать lazy fetching, если вы можете отложить вычисления до тех пор, пока вам действительно не понадобится выполнение, или вы могли бы сделать over-eager evaluation, кэшируя некоторые данные, если вы выполняете одну и ту же операцию слишком часто. Чем лучше вы знаете причину, тем легче ее исправить.

Для вашей конкретной проблемы это может быть только предел функции пользовательского интерфейса ОС. Как правило, кнопка изменения размера - это не только кнопка изменения размера, но и invalidates всей области или ее родительского виджета, чтобы каждый элемент пользовательского интерфейса можно было правильно отобразить. Кнопка изменения размера может быть дорогой, и если это так, вы можете решить проблему, просто используя основанный на изображении подход вместо системы на основе пользовательского интерфейса ОС. Вы можете попробовать использовать OpenGL, если недостаточно операций с изображениями из OS API. Но, опять же, мы не узнаем, пока не попробуем и не попробуем эти альтернативы. Удачи! :)

6 голосов
/ 26 февраля 2011

Попробуйте без тени и посмотрите, не улучшит ли это производительность. Я предполагаю, что это улучшит это очень. Затем я рассмотрел бы использование теневого пути CALayer для рендеринга теней. Это значительно улучшит производительность рендеринга теней.

Видеоролики Apple Core Animation с прошлогоднего WWDC содержат много полезной информации о повышении производительности основной анимации.

Кстати, я сейчас оживляю что-то более сложное, чем это, и это прекрасно работает даже на старом iPhone 3G. Аппаратное / программное обеспечение вполне способно.

1 голос
/ 01 марта 2013

Используете ли вы тени на вашем слое для меня, это было причиной проблемы с производительностью, тогда у вас есть 2 варианта AFAIK: - устанавливая таким образом shadowPath, CA не нужно вычислять его каждый раз - удаление теней и использование изображений для замены

1 голос
/ 15 февраля 2011

Вы, вероятно, должны просто сделать это в OpenGL

0 голосов
/ 15 мая 2017

Я знаю, что этот вопрос старый, но все еще актуален. CoreAnimation (CA) - это просто оболочка вокруг OpenGL - или, тем временем, около Металл .Слои на самом деле представляют собой текстуры, нарисованные на прямоугольниках, а анимации выражены с использованием трехмерных преобразований.Поскольку все это обрабатывается графическим процессором, оно должно быть очень быстрым ... но это не так.Вся подсистема CA кажется довольно сложной, и перевод между AppKit / UIKit и трехмерным миром сложнее, чем кажется (если вы когда-либо пытались написать такую ​​обертку самостоятельно, вы знаете, насколько это сложно).Программисту CA предлагает супер простой в использовании интерфейс, но эта простота имеет свою цену.Все мои попытки оптимизировать очень медленный CA до сих пор были тщетными;Вы можете немного ускорить его, но в какой-то момент вам придется пересмотреть свой подход: либо CA достаточно быстр, чтобы выполнить работу за вас, либо вам нужно прекратить использовать CA и либо самостоятельно реализовать всю анимацию с использованием классического представления чертежа (если процессорможет справиться с этим) или реализовать анимацию самостоятельно, используя 3D API (тогда GPU сделает это), и в этом случае вы можете решить, как трехмерный мир взаимодействует с остальной частью вашего приложения;цена намного больше кода для написания или гораздо более сложный API для использования, но в итоге результаты будут говорить сами за себя.

Тем не менее, я хотел бы дать несколько общих советов по ускорению CA:

  • Каждый раз, когда вы «рисуете» слой или загружаете содержимое в слой (новое изображение), необходимо обновить данные текстуры, поддерживающей этот слой.Каждый 3D программист знает: обновление текстур стоит очень дорого .Избегайте этого любой ценой.
  • Не используйте огромные слои, как если бы слои были слишком большими, чтобы обрабатываться графическим процессором напрямую как единой текстурой, они разбиты на несколько текстур, и это само по себе ухудшает производительность.
  • Не используйте слишком много слоев, так как объем памяти, которую графические процессоры могут тратить на текстуры, часто ограничен.Если вам нужно больше памяти, чем этот предел, текстуры меняются местами (удаляются из памяти графического процессора, чтобы освободить место для других текстур, позже добавляются обратно, когда их нужно рисовать).См. Первый совет выше, этот вид обмена очень дорогой .
  • Не перерисовывайте вещи, которые не нужно перерисовывать, вместо этого кешируйте в изображения.Например, рисование теней и рисование градиентов стоят как очень дорого и обычно редко меняются.Поэтому вместо того, чтобы заставлять CA рисовать их каждый раз, нарисуйте их один раз на слой или нарисуйте их на изображение и загрузите это изображение в CALayer, затем разместите слой там, где он вам нужен.Следите за тем, когда вам нужно обновить их (например, изменился ли размер объекта), а затем перерисуйте их снова и снова кешируйте результат.Сам CA также пытается кэшировать результаты, но вы получите лучшие результаты, если сами контролируете это кэширование.
  • Осторожнее с прозрачностью.Рисование непрозрачного слоя всегда происходит быстрее, чем рисование того, который нет.Поэтому избегайте использования прозрачности там, где это не нужно, поскольку система не будет сканировать весь ваш контент на предмет прозрачности (или ее отсутствия).Если NSView не содержит области, через которую просвечивает его родитель, создайте пользовательский подкласс и переопределите isOpaque, чтобы вернуть YES.То же самое относится к UIView s и слоям, где ни родитель, ни их братья и сестры никогда не будут просвечивать, но здесь достаточно просто установить для свойства opaque значение YES.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...