Подтвердить что ты не робот

С++ для игрового программирования - любовь или недоверие?

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

  • Не используйте интеллектуальные указатели. Никто в играх не делает.
  • Исключения не должны быть (и обычно не используются) в игровом программировании для памяти и скорости.

Сколько из этих утверждений верно? Функции С++ были разработаны с учетом эффективности использования. Является ли эта эффективность недостаточной для игрового программирования? Для 97% игрового программирования?

C-образный подход все еще, похоже, хорошо разбирается в сообществе разработчиков игр. Это правда?

Я смотрел еще одно видео о многоядерном программировании в GDC 2009. Его речь была почти исключительно ориентирована на программирование сотовой связи, где передача DMA необходима перед обработкой (простой доступ указателя не будет работать с SPE ячейки), Он не поощрял использование полиморфизма, поскольку указатель должен быть "повторно основан" для передачи DMA. Как грустно. Это похоже на возвращение к квадрату. Я не знаю, есть ли элегантное решение для программирования полиморфизма С++ на Ячейке. Тема передачи DMA является эзотерической, и у меня здесь не так много опыта.

Я согласен с тем, что С++ тоже не очень понравился программистам, которые хотят, чтобы маленький язык взломал, а не читал стопки книг. Шаблоны также испугали ад из отладки. Согласны ли вы с тем, что С++ слишком боится игрового сообщества?

4b9b3361

Ответ 1

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

Может быть.

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

Вы услышите то же самое о С++ для операционных систем: это слишком медленно, что он делает то, что вы хотите делать хорошо, плохо.

Тем не менее, мы построили OS/400 (начиная с версии v3r6 вперед) полностью на С++, без сбоев и получили базу кода, которая была быстрой, эффективной и малой. Это заняло определенную работу; особенно работая от голого металла, есть некоторые проблемы с начальной загрузкой, использование нового места, такого рода вещи.

С++ может быть проблемой только потому, что она слишком чертовски большая: я перечитываю wristbreaker Stroustrup прямо сейчас, и это довольно запугивает. Но я не думаю, что есть что-то присущее, что говорит, что вы не можете эффективно использовать С++ в игровом программировании.

Ответ 2

Последняя игра, над которой я работал, был Heavenly Sword на PS3 и написан на С++, даже код ячейки. До этого я сделал несколько игр для PS2 и игр для ПК, и они также были С++. Не из проектов использовались интеллектуальные указатели. Не из-за каких-либо проблем с эффективностью, а потому, что они вообще не нужны. Игры, особенно консольные игры, не выполняют динамическое распределение памяти с использованием стандартных менеджеров памяти во время нормальной игры. Если есть динамические объекты (ракеты, враги и т.д.), Они обычно предварительно распределяются и повторно используются по мере необходимости. Каждый тип объекта имеет верхний предел количества экземпляров, с которыми может справиться игра. Эти верхние пределы будут определяться объемом требуемой обработки (слишком много, а игра замедляется до обхода) или количеством ОЗУ (слишком много, и вы можете часто запускать пейджинг на диск, что серьезно ухудшит производительность).

Игры обычно не используют исключения, потому что, ну, игры не должны иметь ошибок и, следовательно, не способны генерировать исключения. Это особенно актуально для консольных игр, в которых игры тестируются производителем консоли, хотя на современных платформах, таких как 360 и PS3, есть несколько игр, которые могут вылететь из строя. Честно говоря, я ничего не читал в Интернете о том, какова фактическая стоимость включения исключений. Если стоимость возникает только тогда, когда возникает исключение, нет причин не использовать их в играх, но я не знаю точно и, вероятно, зависит от используемого компилятора. Как правило, игровые программисты знают, когда могут возникнуть проблемы, которые будут обрабатываться с использованием исключения в бизнес-приложении (например, IO и инициализация) и обрабатывать их без использования исключений (это возможно!).

Но тогда, в глобальном масштабе, С++ медленно уменьшается как язык разработки игры. Flash и Java, вероятно, имеют гораздо больший сегмент рынка, и у них есть исключения и интеллектуальные указатели (в виде управляемых объектов).

Что касается доступа к указателю соты, возникают проблемы, когда код DMA'd в ячейку на произвольных базовых адресах. В этом случае любые указатели в коде должны быть "исправлены" с новым базовым адресом, это включает в себя v-таблицы, и вы действительно не хотите делать это для каждого объекта, который вы загружаете в ячейку. Если код всегда загружается с фиксированным адресом, тогда нет необходимости исправлять указатели. Вы теряете немного гибкости, хотя, как вы ограничиваете, где код может быть сохранен. На ПК код никогда не перемещается во время выполнения, поэтому исправление указателя во время выполнения никогда не требуется.

Я действительно не думаю, что кто-либо "недоверяет" С++-функциям - не доверяя компилятору, - это что-то совсем новое и нередко новое, эзотерические архитектуры, такие как Cell, как правило, получают надежные компиляторы C перед С++, потому что компилятор C намного проще чтобы сделать, чем С++.

Ответ 3

Если вы или ваш друг действительно параноик о производительности, перейдите к руководству Intel по оптимизации. Fun.

В противном случае обращайтесь за правильностью, надежностью и ремонтопригодностью каждый раз. Я бы предпочел, чтобы игра была немного медленнее, чем одна, которая разбилась. Если/когда вы заметили, что у вас есть проблемы с производительностью, PROFILE и затем оптимизируйте. Вы, скорее всего, обнаружите, что theres некоторые кусочки кода hotspot, которые можно сделать более эффективными, используя более эффективную структуру данных или алгоритм. Только беспокоитесь об этой глупой маленькой оптимизации mico, когда профилирование показывает, что они - единственный способ получить достойное ускорение.

Итак:

  • Введите код, чтобы быть понятным и правильным
  • Профиль
  • ПРОФИЛЬ
  • Можете ли вы использовать более эффективные структуры данных или алгоритмы для ускорения узкого места?
  • Использовать микро-оптимизации в качестве последнего средства и только там, где профилирование показало, что это поможет

PS: Многие современные компиляторы С++ предоставляют механизм обработки исключений, который добавляет нулевые служебные служебные данные, если не будет выбрано исключение. То есть производительность снижается только тогда, когда на самом деле возникает исключение. Пока исключения используются только для исключительных обстоятельств, тогда нет веских оснований не использовать их.

Ответ 4

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

Отказ от ответственности: я никогда не программировал игру.

Ответ 5

Я видел сообщение в StackOverflow (которого я больше не могу найти, так что, возможно, он не был размещен здесь), который рассматривал относительную стоимость исключений и кодов ошибок. Слишком часто люди смотрят на "код с исключениями" по сравнению с "кодом без обработки ошибок", что не является справедливым сравнением. Если вы будете использовать исключения, то, не используя их, вы должны использовать что-то еще для одной и той же функциональности, а другое - коды возврата ошибок. Они обнаружили, что даже в простом примере с одним уровнем вызовов функций (поэтому нет необходимости распространять исключения намного выше стека вызовов), исключения были быстрее, чем коды ошибок в случаях, когда возникла ошибка в 0,1% - 0,01% времени или меньше, в то время как коды ошибок были быстрее в противоположной ситуации.

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

Что касается комментария, что в играх нет ошибок и другого программного обеспечения, все, что я могу сказать, это то, что я, очевидно, не играл в какие-либо игры, сделанные их компанией-разработчиком программного обеспечения. Я занялся серфингом на полу элиты 4 в Покемоне, застрял внутри горы в Обливионе, был убит Глоями, которые случайно объединили свой урон от маны с их повреждением hp вместо того, чтобы делать их отдельно в Diablo II и проталкивались закрытые ворота с большой скалой для борьбы с гоблинами с птицей и рогаткой в ​​ "Сумеречной принцессе". У программного обеспечения есть ошибки. Использование исключений не приводит к ошибкам программного обеспечения без ошибок.

Стандартные механизмы исключения библиотек имеют два типа исключений: std::runtime_error и std::logic_error. Я видел, что не хотел использовать std::logic_error (я использовал его как временную вещь, чтобы помочь мне протестировать, с целью ее окончательного удаления, и я также оставил ее в качестве постоянной проверки). std::runtime_error, однако, не является ошибкой. Я генерирую исключение, полученное из std::runtime_error, если сервер, на который я подключен, отправляет мне недействительные данные (правило № 1 безопасного программирования: доверяйте никому, даже серверу, который, как вы думаете, вы написали), например, заявляя, что они меня отправляют сообщение из 12 байт, а затем они действительно посылают мне 15. В такой ситуации есть только две возможности:

1) Я подключен к вредоносному серверу или

2) Мое подключение к серверу повреждено.

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

Я упоминал об ошибке в моем коде? Я так не думаю; Я думаю, что он согласен с тем, что весь другой код, с которым я должен взаимодействовать, несовершенен или злонамерен, и убедитесь, что мой код остается работоспособным перед лицом такой двусмысленности.

Для интеллектуальных указателей, опять же, какие функции вы пытаетесь реализовать? Если вам нужны функциональные возможности интеллектуальных указателей, то использование интеллектуальных указателей означает переписывание их функций вручную. Я думаю, это довольно очевидно, почему это плохая идея. Тем не менее, я редко использую интеллектуальные указатели в своем собственном коде. Единственный раз, когда я действительно делаю, - если мне нужно сохранить некоторый полиморфный класс в стандартном контейнере (скажем, std::map<BattleIds, Battles>, где Battles - это некоторый базовый класс, который получен из основанного на типе битвы), и в этом случае я использовал a std::unique_ptr. Я считаю, что однажды я использовал std::unique_ptr в классе для работы с некоторым кодом библиотеки. В большинстве случаев, когда я использую std::unique_ptr, он должен сделать подвижный подвижный тип, не подлежащий копированию. Однако во многих случаях, когда вы используете интеллектуальный указатель, кажется, что лучше всего создать объект в стеке и полностью удалить указатель из уравнения.

В моем личном кодировании я действительно не нашел много ситуаций, когда версия кода "C" быстрее, чем версия "С++". На самом деле, это вообще противоположное. Например, рассмотрим множество примеров std::sort vs. qsort (общий пример, используемый Бьярне Страуступом), где std::sort clobbers qsort или мое недавнее сравнение std::copy vs. memcpy, где std::copy имеет небольшое преимущество в производительности.

Слишком большая часть "С++-функции X слишком медленная" утверждает, что она основана на сравнении ее с отсутствием функциональности. Наиболее эффективными (с точки зрения скорости и памяти) и без ошибок являются int main() {}, но мы пишем программы для работы. Если вам нужна определенная функциональность, было бы глупо не использовать функции языка, которые предоставляют вам эту функциональность. Однако вы должны начать с размышления о том, что вы хотите, чтобы ваша программа выполняла, а затем найти лучший способ сделать это. Очевидно, что вы не хотите начинать с "Я хочу написать программу, которая использует функцию X из С++" , вы хотите начать с "Я хочу написать программу, которая делает классную вещь Z" , и, может быть, вы закончите "... и лучший способ реализовать это функция X" .

Ответ 6

Что касается архитектуры Cell: у нее есть некогерентный кеш. Каждый SPE имеет собственный локальный магазин объемом 256 КБ. SPE могут получить доступ только к этой памяти; любая другая память, такая как 512 МБ основной памяти или местное хранилище другого SPE, должна быть доступна с помощью DMA. Вы выполняете DMA вручную и копируете память в локальное хранилище, явно инициируя передачу DMA. Это делает синхронизацию огромной болью.

Кроме того, вы можете получить доступ к другой памяти. Основная память и каждое локальное хранилище SPE отображаются в определенную часть 64-разрядного виртуального адресного пространства. Если вы получаете доступ к данным через правильные указатели, DMA происходит за кулисами, и все это выглядит как одно гигантское пространство общей памяти. Проблема? Огромная производительность. Каждый раз, когда вы обращаетесь к одному из этих указателей, SPE останавливается, когда происходит DMA. Это медленно, и это не то, что вы хотите сделать в критическом для производительности коде (например, в игре).

Это приводит нас к пункту Skizz об установках vtables и указателях. Если вы слепо копируете vtable-указатели между SPE, вы понесете огромный успех, если не исправите указатели, и вы также понесете огромный удар по производительности, если вы исправите свой указатели и загрузите код виртуальной функции в SPE.

Ответ 7

Я столкнулся с отличной презентацией Sony под названием "Ловушки объектно-ориентированного программирования". Это поколение консольного оборудования действительно заставило многих людей взглянуть на аспекты OO на С++ и начать задавать вопросы о том, действительно ли это лучший способ продвижения вперед.

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

Ответ 8

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

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

Учитывая достойный компилятор, С++ может быть столь же быстрым, как C, потому что вы можете написать код "C" на С++.

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

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

Я никогда не программировал или не использовал Cell, чтобы иметь дополнительные ограничения и т.д.

Ответ 9

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

Тот факт, что shared_ptr не используется широко, частично объясняется тем, что для него есть реальная стоимость, но что более важно, потому что собственность не очень ясна. Собственность и управление ресурсами - одна из самых важных и трудных вещей, которые нужно сделать правильно. Отчасти потому, что ресурсы по-прежнему недостаточны на консоли, но также и потому, что наиболее сложные ошибки, как правило, связаны с нечетким управлением ресурсами (например, кто и что контролирует время жизни объекта). IMHO shared_ptr не помогает с этим.

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

Но есть много других частей С++, которые широко используются в игровом бизнесе. Внутри EA, EASTL - это потрясающий римейк STL, который очень приспособлен для высокой производительности и скудных ресурсов.

Ответ 10

Существует старая поговорка о том, что генералы полностью готовы сражаться с последней войной, а не следующей.

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

Ответ 11

Это действительно зависит от типа игры. Если это игра с процессором (например, клоун астероидов) или почти что-либо в 2d, вы можете уйти с большим количеством. Разумеется, умные указатели стоят дороже, чем обычные указатели, но если некоторые люди пишут игры на С#, то умные указатели определенно не будут проблемой. И исключения, которые не используются в играх, вероятно, верны, но многие люди злоупотребляют исключениями в любом случае. Исключения должны использоваться только для исключительных обстоятельств. Не ожидаемые ошибки.

Ответ 13

Я также слышал это, прежде чем я присоединился к игровой индустрии, но кое-что, что я нашел, это то, что компиляторы для специализированного игрового оборудования иногда... subpar. (Я лично работал только с основными консолями, но я уверен, что это даже более верно для устройств, таких как мобильные телефоны и т.п.). Очевидно, это не проблема, если вы разрабатываете для ПК, где компиляторы стараются, верны и изобилуют разнообразием, но если вы хотите разработать игру для Wii, PS3 или X360, угадайте, сколько у вас вариантов и насколько хорошо они протестированы по сравнению с вашим компилятором Windows/Unix.

Это не означает, что инструменты обязательно ужасны, конечно, но они гарантированы только на работу, если ваш код прост - по сути, если вы программируете на C. Это не значит, что вы не может использовать класс или создавать интеллектуальный указатель RAII, но чем дальше от этой "гарантированной" функциональности вы получаете, тем легче становится поддержка стандарта. Я лично написал строку кода, используя некоторые шаблоны, скомпилированные для одной платформы, но не с другой - один из них просто не поддерживал какой-либо бахромой случай в стандарте С++ должным образом.

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

Ответ 14

Обработка исключений никогда не бывает бесплатной, несмотря на некоторые утверждения об обратном. ВСЕГДА стоит стоимость, будь то память или скорость. Если он имеет нулевую стоимость исполнения, будет значительная стоимость памяти. В любом случае используемый метод полностью зависит от компилятора и, следовательно, от контроля разработчиков. Ни один из методов не подходит для разработки игр, так как a. целевая платформа имеет ограниченный объем памяти, который часто никогда не бывает достаточным и, следовательно, нам нужен полный контроль и b. фиксированное ограничение производительности 30/60 Гц. Это нормально для ПК-приложения или инструмента для мгновенного замедления, пока что-то обрабатывается, но это абсолютно невыносимо в консольной игре. Существуют физические и графические системы и т.д., Которые зависят от согласованной частоты кадров, поэтому любая функция С++, которая может потенциально нарушить это, и не может контролироваться разработчиком, является хорошим кандидатом на удаление. Если обработка исключений С++ была настолько хороша, что ее стоимость или стоимость невелика или вообще не была, то она использовалась бы в каждой программе, даже не было бы возможности ее отключить. Дело в том, что это может быть аккуратный и аккуратный способ написать надежный код приложения для ПК, но излишний спрос на разработку игры. Он освобождает исполняемый файл, оплачивает память и/или производительность и полностью неоптимаем. Это отлично подходит для ПК-разработчиков, у которых есть огромные кэши команд и тому подобное, но игровые консоли не обладают этой роскошью. И даже если бы они это сделали, сообщество разработчиков игр почти наверняка потратило бы дополнительные циклы/память на ресурсы, связанные с игрой, чем тратить их на функции С++, которые нам не нужны.