Войти
ПрограммированиеФорумОбщее

Применение многопоточности в играх. (Комментарии к статье)

Страницы: 1 2 39 10 Следующая »
#0
6:46, 11 ноя. 2003

Комментарий к Статье Применение многопоточности в играх

Статья посвящена проблеме использования многопоточности при разработке игр. Приводятся общие достоинства и недостатки применения. Автор описал свой подход к реализации многопоточности в игровом движке, который был на практике использован в игре PulseRacer для Xbox.


#1
11:16, 11 ноя. 2003

Сомневаюсь я в увеличении скорости...

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

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

Остается один HyperThreading. Очень существенный аргумент, особенно для игры под XBox! :-). Да и для PC далеко не каждая многопоточность будет использовать возможности HyperThreading. Тут особое проектирование требуется. К томуже тут не требуется МНОГОпоточность. Достаточно всего двух потоков.

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

#2
11:39, 11 ноя. 2003

prVovik
Супер ! Я бы лучше не ответил !
Действительно на современном оборудовании ( PC, XBOX ) Увеличение производительности при использовании многопоточности это просто выдумка автора (и большое заблуждение). Естественно что никто в здравом уме не будет ждать асинхронных операций в основном расчетном потоке, для этого есть ссответствующие инструменты.
Аргументом для многопоточности действительно можно принять максимально быстрый отклик, системы в целом. Но видимо это экзотический случай который можно осуществить на более легкой архитектуре.

Стоит отметить все же что в скором будущем видимо , многопроцессорная (ядерная) архитектура может стать стандартом , в этом направлении работае интел и AMD , или например PS3 архитектура...  Поэтому современным разработчикам уже пора задуматься. К сожалению эффективное программирование многопроцессорных архитектур видимо будет осуществляться методами не так уж схожими с рассмотренными.

#3
11:56, 11 ноя. 2003

Еще верю в такие приложения, как streaming с диска. Пример с CPU и GPU в конце статьи - совсем некорректный.

#4
13:46, 11 ноя. 2003

prVovik
>Сомневаюсь я в увеличении скорости...
Для PulseRacer прирост многопотоковой версии составил 8ms или 20% на кадр, по сравнению с однопопотоковой , хотя это наверно VTune что то напутал.

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

>Но самое главное: необходимо использовать системные функции синхронизации, а это плохо, так как они работают очень медленно! При выполнении почти любой такой функции происходит переход в режим ядра, а это очень тяжелая операция, на которую тратится куча времени.
В моём случае в работающей игре вызов ситемных функций при отсылке команд составил:
96% команд отослались без вызовов системных функций
4% потребовался вызов SetEvent (поток получатель успел заснуть и пришлось будить)
0.001% блокировка внутри критической секции (одновременный вход из двух потоков)
Ничего другого не вызываю. Interlock не всчёт он в ядро не переключает. А если сравнить стоимость этих системных функций со стоимостью вызова функций например DirectX то станет ясно что потери на них в игре будут просто не серьёзными.

>На счет прироста оттого, что в компьютере имеется множество устройств, которые работают вроде бы параллельно. Дак в том то и дело, что они и так уже работают параллельно и асинхронно. Есть соответствующие wait-функции хочешь используй их, а хочешь - не используй.
Хорошо тогда я хочу например ассинхронно вызвать функцию CreateFile что мне для этого нужно сделать?

>Остается один HyperThreading. Очень существенный аргумент, особенно для игры под XBox! :-). Да и для PC далеко не каждая многопоточность будет использовать возможности HyperThreading. Тут особое проектирование требуется. К томуже тут не требуется МНОГОпоточность. Достаточно всего двух потоков.
В играх я  и не создаю кучу потоков, всего несколько - рендер,игра,перефирия и звук, файловые операции, сеть.

>Без сетевых и звуковых потоков ясно дело не обойтись, но это не имеет отношения к производительности.
Чего то не понял то утверждается что в играх не стоит использовать несколько потоков, а тут такое откровение. Или в играх звук и сеть не нужен? Есть идеи почему звук и сеть надо выносить в отдельный поток а рендер не надо?

#5
14:00, 11 ноя. 2003

Семен
>Еще верю в такие приложения, как streaming с диска. Пример с CPU и GPU в конце статьи - совсем некорректный.
В чём состоит не корректность?
Но почему-то у меня в игре он сработал и дал возможность в лишнии 8ms  на кадр, посчитать физику (пока GPU рендерит).

#6
14:21, 11 ноя. 2003

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

#7
14:43, 11 ноя. 2003

Должен извиниться, я невнимательно прочитал часть про рендер. Не увидел, что есть треды. Очень извиняюсь.

Но, тем не менее, если бы обсчет и отправка кадра к GPU были бы в одном треде, распараллеливание все равно было бы. Следующий кадр считался бы во время рендера текущего. И это распараллеливание все же не имеет отношения к тредам. Если все сложнее и я чего-то не понимаю - буду рад комментариям.

#8
15:29, 11 ноя. 2003

Семен
>Но, тем не менее, если бы обсчет и отправка кадра к GPU были бы в одном треде, распараллеливание все равно было бы. Следующий кадр считался бы во время рендера текущего. И это распараллеливание все же не имеет отношения к тредам. Если все сложнее и я чего-то не понимаю - буду рад комментариям.
В процессе отрисовки кадра GPU, управление из функции D3D потоку возвращенно не будет(т.к. нельзя что-то делать с GPU когда он занят, т.е. система будет ждать сигнала от GPU о том что он свободен) и следующий кадр обсчитыватся до завершения операции GPU не будет. Будет простой CPU, который и имеет смысл заюзать, что и делается в многопоточной версии.

#9
15:39, 11 ноя. 2003

minorlogic
Естественно что никто в здравом уме не будет ждать асинхронных операций в основном расчетном потоке, для этого есть сответствующие инструменты.
prVovik
На счет прироста оттого, что в компьютере имеется множество устройств, которые работают вроде бы параллельно. Дак в том то и дело, что они и так уже работают параллельно и асинхронно. Есть соответствующие wait-функции хочешь используй их, а хочешь - не используй.
К сожалению, реальные соответствующие инструменты управления асинхронной работой GPU я видел только в OpenGL 2.0.
То, что предоставляют расширения OpenGL (fences), Direct3D 8 под Xbox (аналоги fences) и Direct3D 9 (Query) - лишь очень слабое подобие того, что действительно необходимо.  В OpenGL 2.0 вопрос решен радикально, как в области синхронизации, так и в области управления ресурсами.
Жаль только, что в Direct3D это придет только где-то к 11 версии.
Устройства (CPU и GPU) работают асинхронно ровно до той стадии, когда приходится их синхронизировать, явно (Lock()) или неявно (любые другие операции).
И что-то я не припомню ни в OpenGL, ни в Direct3D ни одной операции, имеющей "соответствующие wait-функции".  Может, просветите?

Семен
Пример с CPU и GPU в конце статьи - совсем некорректный.
Пример вполне корректный, по крайней мере, для Xbox.  На Xbox есть утилита, позволяющая изучить загрузку и простои GPU.
Под Win32 я этот вопрос не изучал, так что могу поверить в то, что там все иначе.  Хотя (по ощущениям) картина примерно такая же.
Приведенная в статье картинка вполне согласуется с тем, что я наблюдал на Xbox (с точностью до количества кадров, на которое CPU обогнал GPU).
Для fillrate-bound (где мы пролетаем на Present()) и cpu-bound (где мы пролетаем в ожидании заполнения GPU push-buffer) приложений,
CPU фактически простаивает в ожидании GPU.
Самое смешное, что сразу после этого начинает простаивать GPU (пока CPU заполняет его push-buffer командами).
Не думаю, что под Win32 ситуация значительно отличается.

А если взглянуть на вопрос шире, то ведь не важно через что именно вы работаете: через fences, через event'ы, через completion routine.
Один поток у вас или много.
Важно, какова структура вашего кода и данных. Удобно ли вам с ней работать. Прозрачно ли вы взаимодействуете с асинхронными потоками управления.

До определенного момента использование completion routine может заменить вам нормальный многопоточный движок.
Просто код будет выглядеть как макаронная фабрика, сертифицированная по ISO 9000.
В OpenGL 2.0 работа с GPU так же отличается от OpenGL 1.4, как использование event отличается от использования completion routine.
И fences здесь не помогут.

#10
15:40, 11 ноя. 2003

Это не совсем правда. DIP и прочие - только передают данные. Present может синхронизовываться, но может и накопить в себе команды.

Если структура рендеринга такая:

Отдать данные на кадр -> пересчитать след. кадр -> Present ()

то все выгоды распараллеливания есть.

#11
15:46, 11 ноя. 2003

Пред. пост писался до постинга aruslan'a.

Всеж таки. Если мы CPU-bound, то никаких проблем не возникает совсем и Present не ждет совсем. Видимо, проблемы если мы GPU-limited.

Тогда Present () действительно ждет какое-то время и есть CPU stall. Но что предлагается делать в этот stall? Мы и так уже обогнали GPU на 2-3 кадра, так что остается только ждать.
Или борьба идет именно за тот GPU stall, который недолго идет после Present'a, так как действительно CPU не успевает наполнить буфер команд?

#12
16:08, 11 ноя. 2003

Семен
Но что предлагается делать в этот stall? Мы и так уже обогнали GPU на 2-3 кадра, так что остается только ждать.
По этой логике, нам вообще остается всегда только ждать :)
Представь, что ты начал игру УЖЕ с опережением GPU. Надо готовить почву для следующего кадра.
Согласись, что абсолютно неважно, насколько ты опережаешь GPU - на кадр или на три.

Но это в теории.
Если сцены достаточно сложные или просто криво сделаны (что, впрочем, то же самое), тебе, как правило, не удастся обогнать GPU на 2-3 кадра.
Переполнение push buffer, RenderTarget'ы, блокировка буферов и occlusion culling 100% угробят это начинание.
Ну, скажем, occlusion culling на GPU вообще все угробит, так что про него пусть YannL пишет.
С RenderTarget'ами и установкой vertex-shader вообще какая-то лажа у производителей.
С блокировкой буферов под Xbox можно бороться (до определенной степени) аналогами OpenGL immediate mode (glBegin()....glVertex()...glEnd()).
На PC блокировка буферов с DISCARD тоже может спасти, хотя YMMV.
С переполнением буферов команд вообще что-либо сделать трудно.

Борьба идет за CPU-stall с последующим GPU-stall.
В этот "маленький" CPU-stall (до 8 ms) можно сделать достаточно много, чтобы успеть начать озадачивать GPU сразу после его освобождения.

Но, безусловно, YMMV.

Если мы CPU-bound, то никаких проблем не возникает совсем и Present не ждет совсем
Если CPU-bound вызван внутренними причинами (что, как правило, редкость), то да, тогда распараллеливание ничего не даст в принципе.
Если же CPU-bound вызван необходимостью синхронизации с GPU (переполнение push buffer, блокировка ресурсов и т.п.), то здесь мы как раз и можем что-то сделать.
Обрати внимание, что любой реальный вызов D3D (типа DIP и т.п.) потенциально блокирующий.  Причем, надолго.

#13
21:32, 11 ноя. 2003

LFlip
aruslan

Вы слишком много писали для приставок :-)

Напомню, что существует две основные стратегии реализации основного цикла игры:

1) Фиксированный ФПС. При этом физика и АИ обсчитываются фиксированное и заранее определенное число раз в секунду.
2) Плавающий ФПС. При этом физика и АИ обсчитываются столько раз, сколько позволяет аппаратура (зависит от ФПС).

Получить дивиденды от многопоточности можно только при использовании стратегии №1, поскольку только в этом случае ЦПУ может приступить к подготовке нового кадра, не дожидаясь окончания рендеринга. Но стратегия с фиксированным ФПС обладает очень плохой мастабируемостью. Дело в том, что, определяя интервалы обсчета физики и АИ нужно искать компромисс между точностью и нагрузкой на ЦПУ. Проблема в том, что оптимальные значения этих интервалов будут различаться на разных конфигурациях. Отсюда вытекает плохая масштабируемость. Но если в случае с приставками это не страшно, то для PC масштабируемость очень важна!

У варианта №2 проблем с масштабируемостью нет, потому он более всего подходит для PC (и наиболее часто используется). И многопоточность тут - как корове седло, так как рендер должен работать синхронно с физикой.

#14
22:11, 11 ноя. 2003

prVovik
И многопоточность тут - как корове седло, так как рендер должен работать синхронно с физикой.
А звук не должен работать синхронно с физикой?
Почему сам факт наличия некоторого псевдосинхронизирующего примитива типа SwapBuffers() или Present() вызывает у разработчиков настойчивое ощущение необходимости как-то отталкиваться от Present()ов рендера?
А чем плох CommitDeferredSettings() по сравнению с Present()?
Или не нравится другой взгляд на те же яйца, т.е. на иллюзию некоторой сопричастности к тому, что реально в данный момент видит или слышит игрок?

prVovik
Напомню, что существует две основные стратегии реализации основного цикла игры:
1) Фиксированный ФПС. При этом физика и АИ обсчитываются фиксированное и заранее определенное число раз в секунду.
2) Плавающий ФПС. При этом физика и АИ обсчитываются столько раз, сколько позволяет аппаратура (зависит от ФПС).

Бррр... Не увидел связи между фиксированным FPS и количеством интеграционных циклов в игровых процессах.

Во-первых, давайте не путать теплое с мягким.  Фиксированный/плавающий шаг интеграции в физике/AI и фиксированное/плавающее
время на кадр - полностью ортогональные концепции.  Разработчикам физики по барабану, что там делается в рендере.

Во-вторых, использование фиксированного или плавающего FPS - в чистом виде решение разработчика рендера.
Чаще всего как раз получается фиксированно-плавающий, в силу особенностей синхронизации с "обратным ходом луча".
Если это влияет на тех, кто пишет игровые процессы, значит где-то что-то пошло не так.

В-третьих, если из-за GPU случается простой CPU, то физика уже не обсчитывается столько раз, сколько позволяет аппаратура.

Ну и в-четвертых, игровое время и реальное время не должны быть жестко связаны.
Игровое время увеличивается как результат постепенного исполнения игровых процессов.
Рендерится же то, что соответствует определенному значению игрового времени.
Заметь, что если ты посчитал физику для некоего момента game_t и отрендерил мир, то юзер увидет этот мир к момент real_t != game_t для которого считалось.
Так что юзер по-любому живет в прошлом, и здоровая доля релятивизма уже изначально присутствует в идее фиксаций игрового времени на моменты обсчета и рендеринга.

prVovik
Получить дивиденды от многопоточности можно только при использовании стратегии №1, поскольку только в этом случае ЦПУ может приступить к подготовке нового кадра, не дожидаясь окончания рендеринга.
А что мешает приступить к обсчету физики, не дожидаясь окончания кадра? Незнание игрового времени?
А что даст знание времени окончания флипа?
И чем это время принципиально отличается от любой другой произвольно взятой точки на протяжении кадра?
Скажем, момент начала рендеринга сцены? Или момент старта Present()?
Не забывай, что Present() завершится совсем не тогда, когда его результат увидет юзер.

Страницы: 1 2 39 10 Следующая »
ПрограммированиеФорумОбщее

Тема в архиве.