C++ в играх, больших и не
очень
Igor Lobanchikov, 2017
Игорь Лобанчиков
● Разрабатываю игры с 2003 года
● S.T.A.L.K.E.R.: Clear Sky
● Работаю с Confetti
● Эксперт по компьютерной графике
○ Помогаю улучшать и оптимизировать чужие проекты
○ Портирую игры на новые платформы
○ Intel, AMD, Qualcomm, Amazon и другие
● imixerpro(at)gmail(dot)com
О чем будем говорить
● Особенности применения C++ в играх
● Производительность
○ КЭШ: основы
○ Методы снижения производительности с использованием возможностей C++
Кросс-платформенность
● Разные устройства
○ Настольные: Win/Mac/Linux
○ Консоли: PS4/XBox One/Wii/Nintendo Switch
○ Мобильные: iOS/Android
● Разные компиляторы
○ MSVC
○ CLANG/LLVM
○ До недавнего времени GCC
Кросс-платформенность
● Комилятор обновляет владелец платформы
○ CLang/GCC может существенно отставать от выхода PC/Linux версии
○ MSVC под XBox One тоже отстает
● С++ runtime собирает владелец платформы
○ Может содержать баги
● Больше всего проблем с Android
○ Ранние версии Android NDK не содержали STL
○ gnustl поддерживает только С++11 и частично несовместима с Clang
○ Libc++ до сих пор в стадии beta
○ Проблемы при использовании CMake
● Ожидание новых платформ
○ А вдруг там будут проблемы с новыми стандартами
Кросс-платформенность
● Использование новых стандартов создает риски
○ Поддержка на всех платформах
○ Корректность на всех платформах
● Обновление компилятора (и SDK) создает риски
○ Обновление API
○ Android: unified header и Boost
Консервативность и реакционность
● Используется подмножество языка
○ Подмножество STL
○ Или свой собственный STL
○ Или полный запрет на STL
● Используются “устаревшие” стандарты
○ C++11 достаточно “стар”
● Vulcan API - основан на C
● Молодые специалисты недовольны
○ Хотят использовать “современный” инструментарий
Производительность
● Конфликт интересов
○ Картинка должна быть красивой
○ Мир богатым
○ Частота кадров высокой
● 60 кадров в секунду (16.6 мс на кадр)
○ 16.6мс vs 17.6мс = 60 FPS vs 57 FPS
○ 33.3мс vs 34.4мс = 30 FPS vs 29 FPS
● Casual vs AAA
○ Повышение FPS
○ Снижение энергопотребления
● AR приложения
○ Производительность критична
Производительность
● Использование инструментария
○ Оптимизация узких мест
● Использование опыта предыдущей разработки при проектировании
○ Оптимальные решения для целевых платформ
○ Субоптимальные - для иных существующих
○ Спекуляции относительно будущих
КЭШ: основы
СPU0
ISL1 DSL1
СPU1
ISL1 DSL1
L2
СPU2
ISL1 DSL1
СPU3
ISL1 DSL1
L2
L3
RAM
КЭШ: время отклика
Samsung Exynos 5250
4 cycles
21 cycles
...
21 cycles + 110 ns
i7-6700 Skylake
4-5cycles
12 cycles
42 cycles
42 cycles + 51 ns
L1 Data Cache Latency
L2 Cache Latency
L3 Cache Latency
RAM Latency
КЭШ: размеры
Samsung Exynos 5250
32 KB
1 MB
...
i7-6700 Skylake
32 KB
256 KB
8 MB
L1 Data Cache
L2 Cache
L3 Cache
Shared pointer: что из себя представляет
Указатель на блок
управления
Указатель на данные
Блок управления
Управляемый объект
Счетчик shared_ptr
Shared pointer: что из себя представляет
Указатель на блок
управления
Указатель на данные
Блок управления
Управляемый объект
Счетчик shared_ptr
Shared pointer: снижение производительности
void foo(std::shared_ptr<tBar> bar);
Shared pointer: снижение производительности
void foo(std::shared_ptr<tBar> bar);
void foo(const std::shared_ptr<tBar> &bar);
void foo(tBar *bar);
void foo(tBar &bar);
Shared pointer: снижение производительности
struct SortPair
{
size_t sortKey;
boost::shared_ptr<tBar> object;
bool operator< (...) {...}
}
std::vector<SortPair> sort_pairs_vector;
// fill the vector
std::stable_sort(
sort_pairs_vector.begin(),
sort_pairs_vector.end());
Shared pointer: снижение производительности
struct SortPair
{
size_t sortKey;
shared_ptr<tBar> object;
tBar *object;
bool operator< (...) {...}
}
std::vector<SortPair> sort_pairs_vector;
// fill the vector
std::stable_sort(
sort_pairs_vector.begin(),
sort_pairs_vector.end());
Управление временем жизни
● “Старые” графические API (Direct3D 11-, OpenGL / OpenGL ES)
○ Достаточно дорогие вызовы API
○ Дорогие настолько, что использование синхронизации не является критичным
Управление временем жизни
GPU
CPU Client
CPU API
Set Texture A Draw Release Texture A Set Texture B
Draw
API State
Draw
Draw
Управление временем жизни
● “Новый” графические API (Direct3D 12, Vulcan, Metal*)
○ Достаточно дешевые вызовы API
○ Управление временем жизни объекта драйвером существенно влияет на стоимость
вызова
Управление временем жизни
GPU
CPU Client
CPU API
Set Descriptor table Draw
Draw
Draw
Draw
RTTI+исключения
● Традиционно отключаемое в крупных играх
○ Раздувает размер исполняемого файла (до 10%)
○ Влияет на производительность
○ Требует внимания при сборке
RTTI
● Занимает место
○ Для каждого класса с vtable компилятор создает структуру std::type_id
○ Структура содержит строку в качестве идентификатора имени класса
RTTI
● dynamic_cast<> обходит всю иерархию классов
○ Возможно, сравнивая строки для каждого узла (или их хеши)
Class A
Class B
Class C
Class D
Class E
исключения
● SetJump/LongJump
○ потребляет до 10% производительности даже если исключение не будет брошено
● “Zero-cost”
○ раздувает исполняемый файл
○ Если исключение брошено - дополнительные расходы на обработку
○ Рекомендуется использовать максимально редко
Q&A
● вопросы?
ARM Cache -
https://events.linuxfoundation.org/sites/events/files/slides/slides_10.pdf
CPU cache cycles -
http://www.7-cpu.com/
PS4 LLVM -
https://llvm.org/devmtg/2013-11/slides/Robinson-PS4Toolchain.pdf
Exceptions -
https://mortoray.com/2013/09/12/the-true-cost-of-zero-cost-exceptions/
Old style exceptions handling speed -
http://www.codercorner.com/blog/?p=33
clang: no rtti or exceptions
http://llvm.org/docs/CodingStandards.html#ci_rtti_exceptions

C++ в играх, больших и не очень

  • 1.
    C++ в играх,больших и не очень Igor Lobanchikov, 2017
  • 2.
    Игорь Лобанчиков ● Разрабатываюигры с 2003 года ● S.T.A.L.K.E.R.: Clear Sky ● Работаю с Confetti ● Эксперт по компьютерной графике ○ Помогаю улучшать и оптимизировать чужие проекты ○ Портирую игры на новые платформы ○ Intel, AMD, Qualcomm, Amazon и другие ● imixerpro(at)gmail(dot)com
  • 3.
    О чем будемговорить ● Особенности применения C++ в играх ● Производительность ○ КЭШ: основы ○ Методы снижения производительности с использованием возможностей C++
  • 4.
    Кросс-платформенность ● Разные устройства ○Настольные: Win/Mac/Linux ○ Консоли: PS4/XBox One/Wii/Nintendo Switch ○ Мобильные: iOS/Android ● Разные компиляторы ○ MSVC ○ CLANG/LLVM ○ До недавнего времени GCC
  • 5.
    Кросс-платформенность ● Комилятор обновляетвладелец платформы ○ CLang/GCC может существенно отставать от выхода PC/Linux версии ○ MSVC под XBox One тоже отстает ● С++ runtime собирает владелец платформы ○ Может содержать баги ● Больше всего проблем с Android ○ Ранние версии Android NDK не содержали STL ○ gnustl поддерживает только С++11 и частично несовместима с Clang ○ Libc++ до сих пор в стадии beta ○ Проблемы при использовании CMake ● Ожидание новых платформ ○ А вдруг там будут проблемы с новыми стандартами
  • 6.
    Кросс-платформенность ● Использование новыхстандартов создает риски ○ Поддержка на всех платформах ○ Корректность на всех платформах ● Обновление компилятора (и SDK) создает риски ○ Обновление API ○ Android: unified header и Boost
  • 7.
    Консервативность и реакционность ●Используется подмножество языка ○ Подмножество STL ○ Или свой собственный STL ○ Или полный запрет на STL ● Используются “устаревшие” стандарты ○ C++11 достаточно “стар” ● Vulcan API - основан на C ● Молодые специалисты недовольны ○ Хотят использовать “современный” инструментарий
  • 8.
    Производительность ● Конфликт интересов ○Картинка должна быть красивой ○ Мир богатым ○ Частота кадров высокой ● 60 кадров в секунду (16.6 мс на кадр) ○ 16.6мс vs 17.6мс = 60 FPS vs 57 FPS ○ 33.3мс vs 34.4мс = 30 FPS vs 29 FPS ● Casual vs AAA ○ Повышение FPS ○ Снижение энергопотребления ● AR приложения ○ Производительность критична
  • 9.
    Производительность ● Использование инструментария ○Оптимизация узких мест ● Использование опыта предыдущей разработки при проектировании ○ Оптимальные решения для целевых платформ ○ Субоптимальные - для иных существующих ○ Спекуляции относительно будущих
  • 10.
    КЭШ: основы СPU0 ISL1 DSL1 СPU1 ISL1DSL1 L2 СPU2 ISL1 DSL1 СPU3 ISL1 DSL1 L2 L3 RAM
  • 11.
    КЭШ: время отклика SamsungExynos 5250 4 cycles 21 cycles ... 21 cycles + 110 ns i7-6700 Skylake 4-5cycles 12 cycles 42 cycles 42 cycles + 51 ns L1 Data Cache Latency L2 Cache Latency L3 Cache Latency RAM Latency
  • 12.
    КЭШ: размеры Samsung Exynos5250 32 KB 1 MB ... i7-6700 Skylake 32 KB 256 KB 8 MB L1 Data Cache L2 Cache L3 Cache
  • 13.
    Shared pointer: чтоиз себя представляет Указатель на блок управления Указатель на данные Блок управления Управляемый объект Счетчик shared_ptr
  • 14.
    Shared pointer: чтоиз себя представляет Указатель на блок управления Указатель на данные Блок управления Управляемый объект Счетчик shared_ptr
  • 15.
    Shared pointer: снижениепроизводительности void foo(std::shared_ptr<tBar> bar);
  • 16.
    Shared pointer: снижениепроизводительности void foo(std::shared_ptr<tBar> bar); void foo(const std::shared_ptr<tBar> &bar); void foo(tBar *bar); void foo(tBar &bar);
  • 17.
    Shared pointer: снижениепроизводительности struct SortPair { size_t sortKey; boost::shared_ptr<tBar> object; bool operator< (...) {...} } std::vector<SortPair> sort_pairs_vector; // fill the vector std::stable_sort( sort_pairs_vector.begin(), sort_pairs_vector.end());
  • 18.
    Shared pointer: снижениепроизводительности struct SortPair { size_t sortKey; shared_ptr<tBar> object; tBar *object; bool operator< (...) {...} } std::vector<SortPair> sort_pairs_vector; // fill the vector std::stable_sort( sort_pairs_vector.begin(), sort_pairs_vector.end());
  • 19.
    Управление временем жизни ●“Старые” графические API (Direct3D 11-, OpenGL / OpenGL ES) ○ Достаточно дорогие вызовы API ○ Дорогие настолько, что использование синхронизации не является критичным
  • 20.
    Управление временем жизни GPU CPUClient CPU API Set Texture A Draw Release Texture A Set Texture B Draw API State Draw Draw
  • 21.
    Управление временем жизни ●“Новый” графические API (Direct3D 12, Vulcan, Metal*) ○ Достаточно дешевые вызовы API ○ Управление временем жизни объекта драйвером существенно влияет на стоимость вызова
  • 22.
    Управление временем жизни GPU CPUClient CPU API Set Descriptor table Draw Draw Draw Draw
  • 23.
    RTTI+исключения ● Традиционно отключаемоев крупных играх ○ Раздувает размер исполняемого файла (до 10%) ○ Влияет на производительность ○ Требует внимания при сборке
  • 24.
    RTTI ● Занимает место ○Для каждого класса с vtable компилятор создает структуру std::type_id ○ Структура содержит строку в качестве идентификатора имени класса
  • 25.
    RTTI ● dynamic_cast<> обходитвсю иерархию классов ○ Возможно, сравнивая строки для каждого узла (или их хеши) Class A Class B Class C Class D Class E
  • 26.
    исключения ● SetJump/LongJump ○ потребляетдо 10% производительности даже если исключение не будет брошено ● “Zero-cost” ○ раздувает исполняемый файл ○ Если исключение брошено - дополнительные расходы на обработку ○ Рекомендуется использовать максимально редко
  • 27.
  • 28.
    ARM Cache - https://events.linuxfoundation.org/sites/events/files/slides/slides_10.pdf CPUcache cycles - http://www.7-cpu.com/ PS4 LLVM - https://llvm.org/devmtg/2013-11/slides/Robinson-PS4Toolchain.pdf Exceptions - https://mortoray.com/2013/09/12/the-true-cost-of-zero-cost-exceptions/
  • 29.
    Old style exceptionshandling speed - http://www.codercorner.com/blog/?p=33 clang: no rtti or exceptions http://llvm.org/docs/CodingStandards.html#ci_rtti_exceptions

Editor's Notes

  • #4 Постараюсь избегать вкусовщины. Такое может произойти с каждым
  • #5 Разные компиляторы. GCC ушел, пришел LLVM: Нужно поддерживать сразу несколько платформ. Разные компиляторы (в основном MSVC и Clang/LLVM, но может быть GCC, или что-то экзотическое). Разные библиотеки STL Платформы по прежнему отстают в обновлении своих версий компилятора. Негативный опыт: Android NDK вышла без поддержки STL. Неизвестно, что произойдет при появлении новой платформы. (разница в восприятии при разработки крупных проектов и casual) Разработчики также отстают: необходимо гарантировать стабильность окружения, не сломалось - не чини.
  • #6 Зачастую поддержкой компилятора может заниматься один человек! SONY регулярно проводит мержи, вливает исправление багов в основную ветку Обновления отстают в связи с необходимостью тестирования Android: не могу припомнить безпроблемную разработку. Android: проблемы с отладкой, с профилированием.
  • #7 В общем случае непонятно, как изменится производительность, что произойдет со старыми багами. Не известно, сможет ли код вообще собраться (NDK 15 и unified headers. Boost перестал собираться).
  • #8 Ограничения: могут быть экстремальными, вплоть до C-like интерфейсов Vulcan: А вдруг появится платформа без поддержки С
  • #9 На каждом кадре однотипные повторяющиеся вычисления Есть потребность в максимальной эффективности кода. Как следствие - необходимо четкое понимание, что будет происходить за кулисами компилятора. Инфраструктурные потери, размазанные по приложению, практически невозможно исправить в дальнейшем, в отличии от алгоритмических (яркий пример - исключения, которые, до недавнего времени, стоили дорого вне зависимости от их возникновения). Для казуальных vs AAA: узкие места проявляются позже
  • #10 На каждом кадре однотипные повторяющиеся вычисления Есть потребность в максимальной эффективности кода. Как следствие - необходимо четкое понимание, что будет происходить за кулисами компилятора. Инфраструктурные потери, размазанные по приложению, практически невозможно исправить в дальнейшем, в отличии от алгоритмических (яркий пример - исключения, которые, до недавнего времени, стоили дорого вне зависимости от их возникновения). Для казуальных vs AAA: узкие места проявляются позже
  • #11 Затронем только простейшие моменты, необходимо уточнение.
  • #18 Boost
  • #19 Несколько лет назад были проблемы непосредственно с сортировкой. По какой-то причине в момент сортировки дергался счетчик. Сейчас проблема исчезла.
  • #22 Дескрипторы ресурсов и таблицы дескрипторов
  • #27 Каждая функция (почти) регистрирует создание объектов, границы try/catch блоков Zero-cost: таблица, которая по значению PC позволяет определить, какие объекты необходимо разрушить,