Skip to main content

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

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

Чтобы ваша игра работала на Apple Silicon максимально эффективно, вам необходимо оптимизировать свой код. Здесь главное – максимальная эффективность.

Apple Silicon представила новые встроенные графические процессоры и оперативную память для быстрого доступа и повышения производительности. Apple Fabric — это аспект архитектуры M1-M3, который обеспечивает доступ к процессору, графическому процессору и единой памяти без необходимости копирования памяти в другие хранилища, что повышает производительность.

Ядра

Каждый процессор Apple Silicon включает в себя ядра эффективности и ядра производительности. Ядра эффективности предназначены для работы в режиме чрезвычайно низкого энергопотребления, а ядра производительности предназначены для максимально быстрого выполнения кода.

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

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

Ядро и планировщик XNU Ядро микроядра Mach Планировщик выполнения Уровень переносимой операционной системы UNIX POSIX Grand Central Dispatch или GCD (технология потоковой обработки, специфичная для Apple, основанная на блоках) NSObjects Уровень приложения

NSObjects — это основные объекты кода, определенные операционной системой NeXTStep, которую Apple приобрела, когда купила вторую компанию Стива Джобса NeXT в 1997 году.

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

POSIX включает в себя pthreads, которые являются независимыми путями выполнения кода. Объект Apple NSThread — это многопоточный класс, который включает в себя pthreads и некоторую другую информацию о планировании. Вы можете использовать NSThreads и его двоюродный класс NSTask для планирования задач, которые будут выполняться на ядрах ЦП.

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

Методические рекомендации

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

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

Сокращение кода и максимальное планирование выполнения имеют первостепенное значение для обеспечения бесперебойной работы вашей игры.

У Apple есть несколько рекомендаций, которым следует следовать для достижения максимальной эффективности процессора. Эти рекомендации также применимы к компьютерам Mac на базе процессоров Intel.

Время простоя и планирование

Во-первых, когда определенное ядро ​​графического процессора не используется, оно простаивает. Когда он пробуждается для использования, требуется небольшое время пробуждения, что требует небольших затрат. Apple показывает это так:

Далее идет второй тип затрат — планирование. Когда ядро ​​просыпается, планировщику ОС требуется небольшое количество времени, чтобы решить, на каком ядре запускать задачу, затем ему приходится запланировать выполнение кода на ядре и начать выполнение.

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

В-третьих, существует некоторая задержка синхронизации, поскольку планировщик определяет, какие ядра уже выполняют задачи, а какие доступны для новых задач.

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

Вы можете использовать приложение Apple Instruments, чтобы обнаружить и отслеживать, как эти затраты влияют на производительность во время выполнения. Apple показывает пример запущенной игры в Инструментах следующим образом:

Инструменты Apple.

 

В этом примере шаблон потока запуска/ожидания возникает на том же ядре ЦП. Эти задачи можно было бы выполнять параллельно на нескольких ядрах для повышения производительности.

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

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

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

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

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

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

Распараллелить вложенные циклы for

Во время выполнения вложенного цикла кода for-next планирование внешних циклов с более грубой детализацией (т. е. их менее частый запуск) оставляет внутренние части циклов непрерывными. Это может улучшить общую производительность.

Это также уменьшает задержку кэша ЦП и уменьшает количество точек синхронизации потоков.

Пулы заданий и ядро

Apple также рекомендует использовать пулы заданий для повышения производительности рабочих потоков. Рабочий поток — это поток, который запущен или запланирован к скорому запуску и который выполняет некоторую работу во время выполнения кадра.

В пулах заданий рабочие потоки крадут планирование заданий у других потоков. Поскольку для всех потоков существуют определенные затраты на планирование потоков, перехват заданий делает запуск задания в пользовательском пространстве гораздо дешевле, чем в пространстве ядра ОС, где работает планировщик.

Это устраняет накладные расходы на планирование в ядре.

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

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

Избегайте подачи сигналов и ожидания

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

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

Циклы процессора

Далее вам нужно оптимизировать циклы ЦП, чтобы они не тратились зря во время выполнения.

Для этого сначала избегайте продвижения потоков с E-ядра на P-ядро. Электронные ядра работают медленнее, чтобы сэкономить электроэнергию и продлить срок службы батареи.

Вы можете сделать это, избегая циклов ожидания занятости, которые монополизируют ядро ​​ЦП. Если планировщику приходится слишком долго ждать на одном занятом ядре, он может переложить задачу на другое ядро ​​— E-ядро, если оно единственное доступное.

Вызовы планирования доходности и setpri() определяют, с каким приоритетом выполняются потоки и когда следует уступить другим задачам.

Использование yield на платформах Apple эффективно указывает ядру уступить место любому другому потоку, работающему в системе. Такое слабо определенное поведение может создать узкие места в производительности, которые трудно отследить во время выполнения в инструментах.

Производительность доходности варьируется в зависимости от платформы и ОС и может вызывать длительные задержки выполнения — до 10 мс. По возможности избегайте использования yield или setpri(), поскольку это может на мгновение обнулить выполнение данного ядра ЦП.

Кроме того, избегайте использования режима сна(0) — поскольку на платформах Apple он не имеет смысла и не используется.

Масштабирование количество потоков

В общем, вы хотите использовать правильное количество потоков для количества ядер ЦП. Запуск слишком большого количества потоков устройств с малым количеством ядер может снизить производительность.

Слишком много потоков создают переключение контекста ядра, что обходится дорого.

Слишком мало потоков вызывает обратную проблему: слишком мало возможностей для распараллеливания потоков для планирования на нескольких ядрах.

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

Ваш пул потоков всегда должен масштабироваться по количеству ядер ЦП, а не по общему количеству потоков задач.

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

Вы можете запросить устройство iOS или macOS, используя функцию UNIX sysctlbyname. Параметр hw.nperflevels sysctlbyname возвращает информацию о количестве основных ядер ЦП, имеющихся в устройстве.

Используйте Apple Instruments

В приложении Apple Instruments есть шаблон Game Performance, который можно использовать для просмотра и измерения производительности игры во время выполнения.

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

Выводы

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

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

В целом, при планировании игровых заданий на многоядерных системах Apple следует учитывать следующие ключевые моменты:

Сохраняйте задачи как можно меньшими. Группируйте как можно больше мелких задач в один поток. Максимально сокращайте накладные расходы потоков, планирование и синхронизацию. Избегайте циклов простоя/пробуждения ядра. Избегайте переключений контекста потока. Используйте пул заданий. Пробуждайте потоки только при необходимости. Избегайте использования режима сна ( 0) и, если это возможно, используйте семафоры для сигнализации потоков. Масштабируйте количество потоков в соответствии с количеством ядер ЦП. Используйте инструменты.

У Apple также есть видеоролик WWDC под названием «Настройка планирования заданий ЦП для кремниевых игр Apple», в котором обсуждается большинство вышеперечисленных тем и многое другое.

Обращая внимание на особенности планирования вашего игрового кода, вы можете добиться максимальной производительности от своих игр Apple Silicon.