Почему CGBitmapContextCreateImage медленнее, чем [UIImage initWithData:]?

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

Например, если я использую PNG или JPEG, я могу преобразовать полученные данные в UIImage, используя [[UIImage alloc] initWithData:some_data], Когда я использую необработанный байтовый массив или другой пользовательский кодек, который должен сначала декодировать в необработанный байтовый массив, я должен создать растровый контекст, а затем использовать CGBitmapContextCreateImage(bitmapContext) который дает CGImageRef, который затем подается в [[UIImage alloc] initWithImage:cg_image], Это намного медленнее.

Время конвертации изображений

Приведенный выше график (время измеряется в секундах) - это время, необходимое для преобразования из NSData в UIImage. PNG, JPEG, BMP и GIF все примерно одинаковы. Ноль просто не беспокоится о преобразовании и возвращает ноль вместо этого. Raw - это необработанный байтовый массив RGBA, который конвертируется с использованием метода растрового контекста. Пользовательский распаковывается в формат Raw, а затем делает то же самое. LZ4 - это необработанные данные, сжатые с использованием алгоритма LZ4, поэтому он также проходит через метод контекста растрового изображения.

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

Если мы посмотрим на график того, сколько времени требуется для преобразования каждого типа, а также сколько времени требуется для рисования (в графическом контексте), мы получим следующее:

Время рисования и конвертации

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

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

Изменить: для записи, я рисую эти изображения поверх другого UIImage всякий раз, когда я получаю новый. Может быть, есть альтернатива, которая быстрее, которую я готов рассмотреть. Однако, к сожалению, OpenGL не вариант.

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

Окончательное редактирование: у меня возник вопрос, почему не распаковывать и рисовать необработанный массив RGBA быстрее, чем рисовать, например, PNG, поскольку PNG приходится распаковывать в массив RGBA, а затем рисовать. Результатом является то, что это на самом деле быстрее. Тем не менее, это, похоже, имеет место только в сборках релиза. Сборки отладки не оптимизированы для этого, но код UIImage, который работает за кулисами, явно. Компилируя в виде релиза, образы массивов RGBA были намного быстрее, чем другие кодеки.

3 ответа

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

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

UIImage - это не тонкая оболочка для растровых данных, а довольно сложная и оптимизированная система. Основной CGImage может, например, быть только ссылкой на некоторые сжатые данные на диске. Вот почему инициализация UIImage с помощью initWithContentsOfFile: или же initWithData: это быстро. В платформе ImageIO и Quartz в iOS есть более скрытая оптимизация производительности, и все это добавит к вашим измерениям.

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

Вот некоторые соображения, о которых вы должны знать:

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

  • Если изображение отображается с более низким разрешением, чем исходные пиксели, оно может быть распаковано только частично (особенно это возможно в JPEG). Это может быть полезно для оптимизации, но, конечно, не может быть использовано при создании изображений из CGBitmapContext полного разрешения изображения. Так что не делайте этого без необходимости.

  • При измерении с помощью инструментов вы можете не увидеть все соответствующие циклы ЦП. Распаковка изображений может произойти в backboardd (вид оконного сервера, используемый в iOS).

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

Заключение:

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

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

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

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

[UIImage initWithData:] не копирует память. Он просто оставляет память там, где она есть, затем, когда вы рисуете, она сбрасывает память на GPU, чтобы выполнить свою работу - без участия ЦП или ОЗУ в процессе декодирования изображения. Все это делается на выделенном оборудовании GPU.

Помните, что Apple разрабатывает свои собственные CPU/GPU, лицензируя технологии других производителей и настраивая их под свои нужды. У них более тысячи аппаратных инженеров, работающих на одном чипсете, и приоритетной является эффективная обработка изображений.

Ваш низкоуровневый код, вероятно, выполняет много операций по копированию памяти и математике, и поэтому он намного медленнее.

UIImage а также NSData это очень интеллектуальные высокопроизводительные API-интерфейсы, разработанные на протяжении десятилетий людьми, которые действительно понимают (или даже создают) аппаратное и ядро. Они намного более эффективны, чем вы можете достичь с помощью API более низкого уровня, если вы не готовы написать много тысяч строк кода и потратить месяцы или даже годы на тестирование и настройку, чтобы повысить производительность.

NSData например, может легко работать с терабайтами данных с хорошей производительностью, даже если может быть доступно всего несколько гигабайт оперативной памяти - при правильном использовании он будет легко объединять ОЗУ и хранилище SSD/HDD, часто с производительностью, аналогичной той, которую вы получили бы, если бы у вас действительно было терабайт оперативной памяти и UIImage может обнаруживать ситуации с нехваткой памяти и освобождать почти всю ее оперативную память без какого-либо кода от вашего имени - если он знает URL-адрес, с которого изначально было загружено изображение (лучше работает для file:// URLs, чем http:// URLs).

Если вы можете делать то, что вы хотите с UIImage а также NSDataтогда вам следует. Используйте API нижнего уровня только в том случае, если у вас есть функция, которую вы не можете реализовать другим способом.

Другие вопросы по тегам