Устранение задержки синхронизации центрального и графического процессоров в игре Galactic Civilizations* 3

Download Document

Galactic Civilizations* 3 (GC3) — это пошаговая глобальная стратегия, разработанная и выпущенная компанией Stardock Entertainment. Игра увидела свет 14 мая 2015 г. В ходе демонстрационного доступа и бета-тестирования мы собирали и анализировали информацию о производительности процессов рендеринга в этой игре. Одним из основных улучшений, которые нам удалось осуществить, стало устранение нескольких источников задержки синхронизации центрального и графического процессоров, в результате которой нарушался параллелизм в работе процессоров. В этой статье описывается выявленная проблема и найденное решение, а также рассматривается важность использования средств анализа производительности в процессе разработки с учетом преимуществ и недостатков этих средств.

Выявление проблемы

Мы начали изучение эффективности рендеринга с помощью анализаторов производительности графической подсистемы Graphics Platform Analyzers (GPA), входящих в состав пакета Intel® INDE. На снимке экрана внизу представлены данные трассировки (без вертикальной синхронизации) до внедрения усовершенствований. В очереди графического процессора наблюдаются промежутки внутри кадров и между ними, причем в каждый момент времени объем отложенной нагрузки составляет менее одного кадра. Если очередь графического процессора не получает достаточного объема ресурсов от центрального процессора, возникают временные промежутки, которые не могут использоваться приложением для повышения производительности и точности рендеринга.


До:длительность кадра — около 21 мс; длина очереди — менее 1 кадра; промежутки в очереди графического процессора; слишком длительные вызовы метода Map

Кроме того, в интерфейсе GPA Platform Analyzer показывается время, затрачиваемое на обработку каждого вызова API-интерфейса Direct3D* 11 (т. е. передачи каждой команды по пути «приложение — среда выполнения — драйвер» и получения отклика). На приведенном снимке экрана показан вызов метода ID3D11DeviceContext::Map, который занимает около 15 мс вместе с получением отклика. В течение этого времени основной поток приложения простаивает.

На снимке ниже показана увеличенная временная шкала с интервалом обработки одного кадра (от начала операции, выполняемой центральным процессором, до окончания операции, выполняемой графическим процессором). Промежутки простоя отмечены розовыми прямоугольниками, их общая длительность составляет около 3,5 мс на кадр. Средство Platform Analyzer также отображает общую длительность вызовов различных API-интерфейсов в данной трассе (4,306 секунды), из которой вызовы Map занимают 4,015 секунды!

Необходимо отметить, что средство Frame Analyzer не может выявить длительный вызов Map с помощью захвата кадра. Средство Frame Analyzer запрашивает данные таймера графического процессора для измерения времени, затрачиваемого на выполнение эрга, который включает в себя изменения состояния, привязку ресурсов и рендеринг. Вызовы Map выполняются центральным процессором без участия графического процессора.

Поиск источника проблемы

(В разделе, посвященном ресурсам Direct3D, в конце статьи вы найдете базовые инструкции по использованию и обновлению ресурсов.)

Средство отладки драйвера выявило, что длительный вызов Map использует флаг DX11_MAP_WRITE_DISCARD (в интерфейсе Platform Analyzer не отображаются аргументы вызова Map) для обновления крупного буфера вершин, созданного с помощью флага D3D11_USAGE_DYNAMIC.

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

Именно в этом состояла проблема в игре Galactic Civilizations 3. При каждом возникновении подобной ситуации (то есть несколько раз за время обработки каждого кадра для нескольких крупных ресурсов, которые сопоставлялись многократно) драйвер ожидал, пока метод Draw, использующий ранее назначенный алиас ресурса, завершит процесс, чтобы использо­вать этот алиас для другого запроса. Данная проблема возникала не только с драйвером Intel. Она также возникала с драйвером NVIDIA, и в этом случае мы использовали средство GPUView, чтобы подтвердить данные, полученные с помощью анализатора Platform Analyzer.

Размер буфера вершин составлял около 560 КБ (определено с помощью драйвера), а сопоставление с буфером выполнялось примерно 50 раз за время обработки одного кадра (со сбросом). Для хранения алиасов драйвер Intel выделяет по требованию несколько куч (размером 1 МБ каждая) на ресурс. Алиасы выделяются из кучи до достижения предельного объема, после чего ресурсу назначается другая теневая куча алиасов размером 1 МБ и так далее. В случае с длительным вызовом Map куча вмещала в себя не более одного алиаса, поэтому каждый раз, когда метод Map обращался к ресурсу, создавалась новая теневая куча для нового алиаса до тех пор, пока не достигалось предельное количество куч. Это происходило при обработке каждого кадра (этим объясняется повторение конфигурации на диаграмме). При каждом обращении драйвер ожидал, пока предыдущий вызов Draw (выполняемый для того же кадра) завершит использование алиаса, чтобы использовать его повторно.

Мы изучили журнал API-интерфейса в средстве Frame Analyzer и отсортировали ресурсы, которые сопоставлялись несколько раз. Обнаружилось несколько случаев, когда сопоставление с буфером вершин выполнялось более 50 раз, причем основным источником проблемы оказалась система интерфейса пользователя. Средство отладки драйвера выявило, что при каждом сопоставлении обновлялся только небольшой фрагмент буфера.


Один и тот же ресурс (с идентификатором 2322) многократно сопоставляется за время обработки одного кадра

Решение проблемы

Мы в Stardock выполнили настройку всех систем визуализации для отображения дополнительных маркеров на временной шкале Platform Analyzer, в частности для того, чтобы убедиться, что источником слишком длительного вызова был пользовательский интерфейс, а также для создания профилей в будущем.

У нас было несколько возможных способов решить проблему.

  • Можно было заменить флаг D3D11_MAP_WRITE_DISCARD на D3D11_MAP_WRITE_NO_OVERWRITE для вызова Map. Крупный буфер вершин используется несколькими схожими элементами. Например, большинство элементов пользовательского интерфейса на экране совместно используют крупный буфер. Каждый вызов Map обновляет лишь небольшой отдельный фрагмент буфера. Космические корабли и астероиды, для которых применяется технология хранения экземпляров, также используют крупный буфер вершин (данных экземпляров). В данном случае флаг D3D11_MAP_WRITE_NO_OVERWRITE был бы идеальным решением, поскольку он обеспечил бы защиту фрагментов буфера, которые могут использоваться графическим процессором в данный момент, от перезаписи приложением.
  • Существовала возможность разделить крупный буфер вершин на несколько мелких.
    Поскольку причина сбоя синхронизации заключалась в выделении алиасов, благодаря значительному сокращению размера буфера вершин куча смогла бы вмещать несколько алиасов. Число вызовов Draw в приложении Galactic Civilizations 3 ограничено, поэтому сокращение размера буфера в 10 или в 100 раз (с 560 КБ до
    5–50 КБ) позволяло решить проблему.
  • Еще один вариант заключался в использовании флага D3D11_MAP_FLAG_DO_NOT_WAIT.
    С его помощью можно определить, используется ли данный ресурс графическим процессором, и выполнить другую задачу, прежде чем ресурс освободится для нового сопоставления. Несмотря на то что в данном случае нагрузка выполняется центральным процессором, это решение было далеко не оптимальным для данной проблемы.

Мы выбрали второй вариант и заменили константу в алгоритме создания буфера. Размеры буферов вершин для каждой подсистемы были жестко запрограммированы, их требовалось только снизить. Теперь каждая куча размером 1 МБ могла вмещать несколько алиасов и, благодаря сравнительно небольшому числу вызовов Draw в приложении Galactic Civilizations 3, проблема должна была исчезнуть.

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


После: длительность кадра — около 16 мс; длина очереди — 3 кадра; отсутствие промежутков в очереди графического процессора; отсутствие длительных вызовов метода Map

 

Общая длительность вызовов метода Map сократилась с 4 секунд до 157 миллисекунд! Исчезли задержки в очереди графического процессора. Длительность очереди стабильно составляла 3 кадра, и по окончании обработки кадра графическим процессором следующий кадр уже ждал своей очереди! Несколько несложных изменений помогли обеспечить непрерывную работу графического процессора. Повышение быстродействия составило около 24 %: время обработки каждого кадра сократилось примерно с 21 до 16 мс.

Важность использования средств анализа производительности графического процессора при разработке игр

Ниже приводится отзыв компании Stardock:

«Без таких средств, как GPA Platform Analyzer и GPUView, мы не смогли бы получить точные данные о работе графического процессора, поскольку платформа DirectX позволяет уточнить только один параметр — успешно ли выполнен метод. Ранее нам приходилось отключать системы или подсистемы, чтобы выявить причину снижения быстродействия. Этот процесс отнимает много времени — от нескольких часов до нескольких дней — без каких-либо практических результатов, особенно в том случае, если выбранная система не является источником проблемы.

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

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

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

Заключение

Оптимизация производительности процессов визуализации в играх представляет собой непростую задачу. Средства захвата и воспроизведения кадров и трасс предоставляют различные важные сведения о производительности игры. В этой статье были рассмотрены задержки синхронизации центрального и графического процессоров, для диагностики которых требуются такие средства трассировки, как GPA Platform Analyzer или GPUView.

Слова благодарности

Благодарим вице-президента компании Stardock Entertainment Дерека Пэкстона (Derek Paxton) и руководителя отдела разработки графики Джесса Бриндла (Jesse Brindle) за сотрудничество и возможность внедрить описанные усовершенствования в игру Galactic Civilizations 3.

Особая благодарность Роберту Блейку Тейлору (Robert Blake Taylor) за отладку драйвера, Роману Борисову и Джеффри Фриману (Jeffrey Freeman) за рекомендации по использованию анализаторов производительности графической подсистемы, а также сотрудникам Intel Акселю Мамоду (Axel Mamode) и Джеффу Лафламу (Jeff Laflam), которые рецензировали эту статью.

Об авторе

Раджа Бала — инженер-разработчик приложений в отделе взаимодействия с разработчиками игр в компании Intel. Ему нравится анализировать процессы рендеринга в играх и находить способы их ускорения. Кроме того, он увлекается Dota 2* и другими играми Valve.

Основные сведения о работе с ресурсами Direct3D*

В API-интерфейсе Direct3D можно выделить средства создания и удаления ресурсов, задания состояния конвейера рендеринга, привязки ресурсов к элементам конвейера, а также средства обновления определенных ресурсов. Большинство операций по созданию ресурсов выполняется во время загрузки уровней и сцен.

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

Во время создания ресурса элемент перечисления D3D11_USAGE используется для задания следующих параметров ресурса:

(а)   доступ ГП для чтения и записи (DEFAULT — для объектов визуализации, элементов UAV, редко обновляемых буферов констант);

(б)   доступ ГП только для чтения (IMMUTABLE — для текстур);

(в)   доступ ЦП для записи + доступ ГП для чтения (DYNAMIC — для часто обновляемых буферов);

(г)   доступ ЦП с возможностью для ГП копировать данные в ресурс (STAGING).

Обратите внимание, что для обеспечения вариантов использования (в) и (г) необходимо правильно установить флаг D3D11_CPU_ACCESS_FLAG для ресурса.

Для обновления данных ресурса в API-интерфейсе Direct3D 11 предусмотрено три метода, каждый из которых выполняет определенные задачи (как описано выше):

(i) Map/Unmap;
(ii) UpdateSubresource;
(iii) CopyResource/CopySubresourceRegion.

 

Существует интересный сценарий, требующий неявной синхронизации: когда ЦП имеет доступ к ресурсу для записи, а ГП — для чтения. Подобный сценарий часто встречается во время обработки кадров. В качестве примеров можно привести обновление матрицы представления (модели, проекции) или преобразование связующей матрицы модели (model bone) в анимации. Ожидание, пока графический процессор завершит использование ресурса, привело бы к неоправданному снижению быстродействия. Создание нескольких независимых ресурсов (копий ресурса) для реализации этого сценария слишком усложнило бы задачу для создателей приложения. В результате в интерфейсе Direct3D версий 9–11 эта задача передается на драйвер с помощью флага DX11_MAP_WRITE_DISCARD. Каждый раз, когда сопоставление ресурса выполняется с помощью этого флага, драйвер создает новую область памяти для ресурса, которую использует центральный процессор. Таким образом, различные вызовы Draw, которые обновляют данный ресурс, используют разные псевдонимы ресурса, что, несомненно, повышает коэффициент использования памяти ГП.

Дополнительные сведения об управлении ресурсами в Direct3D:

презентация Джона Макдональда (John McDonald) «Эффективные средства управления буфером» (Efficient Buffer Management) на конференции GDC;
основы управления ресурсами в Direct3D 11;
выбор ресурса в Direct3D 10;
методы UpdateSubresource и Map.

 

Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.