Для тех, кто из-за большого перерыва между статьями уже забыл, напомню — в прошлый раз мы начали знакомство с Quartz — графической подсистемой Mac OS X. Я рассказал о том, как в целом устроен Quartz, из каких частей состоит, и за что каждая из этих частей в итоге отвечает, а также показал на примере Mac OS X 10.0 (Cheetah) как собственно был реализован Quartz, и с какими проблемами пришлось столкнуться разработчикам. С проблем-то и начнём.
Основная проблема с Quartz в первой версии Mac OS X заключалась в том, что работало всё довольно таки медленно (помните тест с полупрозрачной консолью и QuickTime-роликом?). Центральному процессору приходилось трудиться за себя и за того парня, выполняя команды рисования Quartz2D, записывая результаты их работы в backing store, а за тем — выполнять композитинг всех окон в финальную сцену. Разумеется, долго так продолжаться не могло, и вот как поступили разработчики Quartz в Mac OS X 10.2 (в версии 10.1 особых изменений в работе Quartz не было за исключением некоторых общих оптимизаций).

Как видно из рисунка, теперь Quartz Compositor целиком выполняется на GPU видеокарты. Этого удалось добиться, переделав Compositor в OpenGL-приложение, для которого каждое окно представляло собой двухмерную поверхность, а содержимое backing store для этого окна —текстуру этой поверхности. Эту версию Quartz назвали Quartz Extreme, и она работала только на видеокартах, поддерживавших текстуры произвольного размера (так как окно, по сути, также может быть любого размера) и AGP 2x, для того, чтобы иметь возможность читать содержимое backing store напрямую из оперативной памяти.
Перенос Quartz Compositor на видеокарту сильно разгрузил центральный процессор. Выполнение операций по блендингу больших битмапов — с этим GPU справляется очень и очень хорошо (для этого его и придумали). Более того, центральный процессор перестал заниматься и переносом backing stores из оперативной в видеопамять, а это тогда значило очень много (когда частота системной шины не превышала 133 мегагерца). Возвращась к тесту с полупрозрачным окном терминала и видеороликом: на том же самом компьютере, но со включенным Quartz Extreme, fps видео вообще не менялся, вне зависимости от того, сколько полупрозрачных окон было размещено поверх него. Самые отчаянные экспериментаторы доходили до 25 окон, но fps ролика оставался невозмутимым — 24 кадра в секунду.
Mac OS X 10.3 (Panther) тоже не принесла никаких изменений в работе Quartz (как и 10.1), кроме общей оптимизации. Посмотрим теперь, что произошло в 10.4 под кодовым названием Tiger.
Quartz Extreme отлично справлялся с работой по композитингу backing stores в финальную сцену, однако загвоздка в том, что backing store не заполняется по мановению волшебной палочки — его содержимое сначала кто-то должен нарисовать. Представим, например, окно браузера с текстом, картинками, элементами управления и тому подобным. Если только окно не является полупрозрачным (или находится под другим полупрозрачным окном), трудоёмкость композитинга меркнет в сравнении с вычислительной сложностью задачи отрисовки содержимого окна в backing store. Рассмотрим в качестве примера текст. Каждый из этих символов должен быть растеризован из своего векторного описания в файле шрифта, к этому растру должно быть применено сглаживание, а затем он должен быть помещён на саму страницу с учётом фона (то есть, по сути — должен быть выполнен композитинг для этого конкретного символа внутри окна браузера). Всю эту работу приходится выполнять CPU, так как Quartz Compositor имеет дело с уже заполненными backing stores. Более того, содержимое окна перерисовывается каждый раз, когда, например, меняется его размер или выполняется прокрутка его содержимого. И в этом случае Quartz Extreme ничем не может нам помочь — он просто мучительно долго (в сравнении с CPU) ждёт, пока центральный процессор закончит рисовать в backing store, а затем очень быстро выполняет композитинг финальной сцены и ждёт следующей итерации.
Ещё одна проблема становится видна, если посмотреть на схему работы Quartz Extreme, обращая особое внимание на ширину полосы пропускания (напоминаю, что конкретные цифры приведены исключительно для относительного сравнения) и сопоставляя её с объёмами данных на каждом участке. Результаты вызовов API Quartz2D, по сути, представляют собой большие растровые изображения, которым нужна довольно таки широкая полоса. Сами вызовы Quartz2D довольно маленькие по объёму, и большой полосы пропускания им не надо. На рисунке же мы видим, что самое узкое место (между оперативной и видеопамятью) используется для передачи самых больших объёмов данных (заполненных backing stores).
Наконец, учитывая то, что backing stores обрабатываются на двух разных процессорах (CPU и GPU), с раздельной памятью (RAM и VRAM), можно догадаться, что эта схема работы обладает высокими накладными расходами на синхронизацию. Пока с backing store окна работает Quartz Compositor, приложение рисовать туда не может, и наоборот — Quartz Compositor не может начать выполнять композитинг до тех пор, пока приложение не закончило рисование.
Решение всех этих проблем может показаться очевидным — достаточно поместить Quartz2D в часть диаграммы, где больше всего жирных красных линий — на видеокарту, решив тем самым проблемы с пропускной способностью и синхронизацией. Итак, встречайте — Quartz2D Extreme.

Глядя на картинку можно заметить, как всё больше и больше задач в Quartz выполняется на видеокарте. Объясняется это очень просто — достаточно посмотреть на величины ширины полосы пропускания между GPU и видеопамятью. Причём, эта величина продолжает расти, и расти гораздо быстрее, чем полоса пропускания между центральным процессором и RAM.
Как мы видим, вызовы Quartz2D теперь выполняются на GPU, а центральному процессору остаётся только отправлять сравнительно небольшие по объёму команды рисования через драйвер на видеокарту. Естественно, не было бы смысла перемещать Quartz2D на видеокарту, а backing stores хранить по-прежнему в RAM, поэтому и они теперь перехали в видеопамять. Помимо того, что теперь отрисованные backing stores передаются по широким шинам между GPU и видеопамятью, разработчики Quartz ещё и существенно снизили задержки на синхронизацию, ибо теперь и Quartz2D, и Quartz Compositor выполняются на одном процессоре (GPU) и пишут/читают из одной и той же памяти (VRAM).
Казалось бы — победа. Однако, как это часто бывает в жизни, всё оказалось не так просто. Во-первых, центральный процессор не всегда передаёт только маленькие команды рисования для Quartz2D. Самый распространённый пример — растровые изображения. Представим ситуацию — изображение на диске, а backing store рисующего приложения — в видеопамяти. Как оно может туда попасть? Напрямую с диска видеокарта читать не умеет, поэтому эту операцию приходится выполнять всё тому же CPU, сначала загружая изображение в оперативную память, а потом передавая её по узкой шине между оперативной памятью и видеокартой. Таким образом, если CPU придётся часто передавать большие растровые изображения, это может уничтожить все преимущества в скорости, которые даёт использование Quartz2D Extreme. Решение оказалось довольно таки простым — такие растровые изображения, будучи переданными на видеокарту один раз, кэшируются в видеопамяти, и затем Quartz использует только локальную копию в VRAM.
Но тут перед нами встаёт другое проявление суровой реальности — видеопамять, к сожалению, конечна, и постоянно кэшировать в ней большие растры не представляется возможным. Что произойдёт, если объёма VRAM окажется недостаточно для хранения всех backing stores, закэшированных битмапов, отрендеренных шрифтов и так далее? Нетрудно заметить, что относительно недавно (в масштабах вечности, разумеется) такая же проблема вставала с другой памятью — оперативной. Решение нам всем хорошо знакомо — виртуализация. Таким же образом поступили и разработчики Quartz — те данные, которые не уместились в видеопамяти, сбрасываются на более медленный носитель — оперативную память. Разумеется, Quartz старается размещать в видеопамяти не любые данные по принципу FIFO, а именно те, которые используются чаще всего. А когда видеопамяти всё-таки оказывается недостаточно, то начинается печально известный «хронический пэйджинг», который знаком каждому, кто пытался запустить какую-нибудь современную ОС на компьютере с 256 мегабайтами оперативной памяти, разве что диск не шумит.
Учитывая всё вышесказанное, какой же выйгрыш даёт применение Quartz2D Extreme (и даёт ли вообще)? Посмотрим на результаты синтетических тестов в следующей таблице:
| |
Quartz2D
Тысяч операций в секунду |
Quartz2D Extreme
Тысяч операций в секунду |
Ускорение |
| Прямоугольник 100×100 |
40 |
185 |
4.6x |
| Прямоугольник 800×800 |
2 |
472 |
236x |
| Узор 8×8 |
42 |
177 |
4.2x |
| Картинка 128×128 |
32 |
392 |
12x |
| Строка из 12 символов |
1,733 |
4,720 |
2.7x |
| Линии |
1,700 |
14,900 |
9x |
В принципе, все результаты являются предсказуемыми, а особенно выделяется тест на заполнение прямоугольника размером 800 на 800 пикселей. По сути, этот тест проверяет, как быстро Quartz сможет нарисовать 640 тысяч пикселей в backing store, пропустить его и другие backing stores через Quartz Compositor и вывести всё это на экран. В предыдущих реализациях Quartz, центральному процессору пришлось бы рисовать этот прямоугольник в backing store, который находится в оперативной памяти, а затем передавать его на видеокарту, чтобы Quartz Compositor мог выполнить композитинг и выполнить вывод на экран; теперь же центральному процессору достаточно передать только одну OpenGL-команду на рисование прямоугольника в Quartz2D Extreme, который отрисует прямоугольник прямо в видеопамять — как раз туда, где его уже ждёт Quartz Compositor. Стоит обратить внимание ещё на одну деталь. Строка из 12 символов рисуется «всего» в 2,7 раза быстрее с помощью Quartz2D Extreme, что показывает, насколько хорошо в Tiger оптимизирована софтверная реализация Quartz2D (без Extreme). Это становится ещё заметнее, если сравнить её с софтверной реализацией Quartz2D в предыдущей версии Mac OS, Panther. Обратимся к результатам теста на рисование линий:

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

За 12 миллионами линий в секунду, которые успевает отрисовать Quartz2D Extreme, трудно разглядеть, что тот самый софтверный Quartz2D, который в предыдущей версии был медленнее QuickDraw почти в два раза, теперь стал быстрее его в пять раз. В итоге имеем неплохой прирост производительности в 10 раз.
Ну, а теперь самый животрепещущий вопрос — повысились ли удои? Виден ли этот бешеный рост производительности в реальных приложениях? Так вот — разница вовсе не так заметна, а некоторые приложения стали работать _медленнее_ при включенном Quartz2D Extreme. В чём же проблема? А проблема как раз в кэшировании битмапов в видеопамяти. Дело в том, что Quartz без посторонней помощи не может предсказать (хотя и старается), какие битмапы будут использоваться приложением в ближайшем будущем. Чтобы знать об этом, приложение должно сигнализировать Quartz, какие битмапы ещё будут вскоре использоваться, сохраняя ссылки на них (тут я поясню, что в CoreGraphics — API, которое позволяет получить доступ к функциям Quartz2D — да и во многих других API Mac OS реализован механизм счётчика ссылок). А это прямо противоречит установившейся практике, когда память из под использованных битмапов освобождалась сразу, а потом, при необходимости, изображение снова читалось с диска (тут ещё и хороший дисковый кэш помогал). Однако, с момента выхода Tiger, разработчики приложений для Mac OS X (особенно усиленно работающих с графикой) уже поправили эти моменты, и теперь всё работает, как по маслу.
Вот, пожалуй, и всё, что я хотел рассказать вам про Quartz — графическую подсистему Mac OS X. Самое интересное, что предложенная в Tiger реализация без каких-либо особых изменений продолжает работать и в последней версии Mac OS X — Snow Leopard, много раз в секунду что-то рисуя, копируя, кэшируя, выполняя композитинг и выводя всё это безобразие на экран.
В следующей серии статей мы поговорим об API и инструментах разработчика, которые предоставляет Mac OS X нам, программистам; ну а пока, как обычно, я приглашаю всех к обсуждению статьи в комментариях. Stay tuned!

Recent Comments