游戏编程的C++ - 爱还是不信任?

ami*_*mit 29 c++ performance

在游戏编程效率的名义下,一些程序员不信任几个C++特性.我的一位朋友声称了解游戏行业的运作方式,并会提出以下意见:

  • 不要使用智能指针.游戏中没有人这样做.
  • 在游戏编程中,例外不应该(并且通常不是)用于记忆和速度.

这些陈述中有多少是真的?设计C++功能时要牢记效率.这种效率不足以进行游戏编程吗?97%的游戏编程?

C-way-of-thinking仍然似乎对游戏开发社区有很好的把握.这是真的?

我在GDC 2009上观看了关于多核编程的另一个视频.他的演讲几乎完全针对单元编程,在处理之前需要进行DMA传输(简单的指针访问不适用于Cell的SPE).他不鼓励使用多态,因为指针必须"重新基于"DMA传输.多么悲伤.这就像回到广场一样.我不知道是否有一个优雅的解决方案来编程Cell上的C++多态.DMA传输的主题是深奥的,我在这里没有太多背景.

我同意C++对于那些希望使用小语言进行破解而不是阅读书籍堆栈的程序员来说也不是很好.模板也吓坏了调试.你是否同意游戏社区过分担心C++?

Ski*_*izz 63

我工作的最后一个游戏是PS3上的Heavenly Sword,它是用C++编写的,甚至是单元代码.在此之前,我做了一些PS2游戏和PC游戏,他们也是C++.非项目使用智能指针.不是因为任何效率问题,而是因为它们通常不需要.游戏,尤其是控制台游戏,在正常播放期间不使用标准内存管理器进行动态内存分配.如果有动态物体(导弹,敌人等),那么它们通常是预先分配的,并根据需要重新使用.每种类型的对象都会对游戏可以处理的实例数量设置上限.这些上限将由所需的处理量(太多而游戏速度慢到爬行)或RAM存在量(太多而且您可能经常开始寻呼到磁盘,这会严重降低性能)来定义.

游戏通常不使用例外,因为游戏不应该有错误,因此不能产生异常.对于控制台制造商测试游戏的控制台游戏尤其如此,尽管最近的平台如360和PS3似乎确实有一些游戏可能会崩溃.说实话,我没有在网上看到任何启用例外的实际成本是什么.如果仅在抛出异常时产生成本,那么没有理由不在游戏中使用它们,但我不确定并且它可能取决于所使用的编译器.通常,游戏程序员知道何时可以使用业务应用程序中的异常(例如IO和初始化)处理问题并在不使用异常的情况下处理它们(可能!).

但是,在全球范围内,C++正逐渐减少作为游戏开发的语言.Flash和Java可能拥有更大的市场份额,它们确实有异常和智能指针(以托管对象的形式).

至于Cell指针访问,当代码在任意基地址被DMA进入Cell时会出现问题.在这种情况下,代码中的任何指针都需要与新的基地址"固定",这包括v表,并且您并不真正想要为加载到Cell中的每个对象执行此操作.如果代码总是在固定地址加载,那么就不需要修复指针.虽然因为限制了代码存储的位置,但您会失去一点灵活性.在PC上,代码在执行期间永远不会移动,因此永远不需要在运行时修复指针.

我真的不认为任何人不信任'C++特性 - 不相信编译器完全是另外一些新的,像Cell这样的深奥体系结构往往会在C++之前获得强大的C编译器,因为C编译器比C编译器更容易制作一个C++的.

  • "游戏通常不会使用例外,因为游戏不应该有错误"软件通常不应该有错误,但它最终会有错误.仔细使用异常是处理错误的好方法,不会因为大量错误处理而使代码混乱. (9认同)
  • 关于上述内容:制造商对控制台游戏进行了彻底的测试,任何失败都意味着不允许发布游戏,这意味着没有从销售中获得任何收入.所以游戏通常没有bug.测试涵盖了所有已知的故障模式,例如网络游戏的网络断开,控制器移除,存储卡移除等.绝不应该发生内存 - (XBox除外)控制台有一个已知的,固定数量的RAM,并且在开发过程中(音频,图形,AI等)仔细预算游戏的各个方面. (7认同)
  • @Skizz:可能Amit不同意"游戏不应该有错误,因此无法产生异常"所固有的假设.一些程序员在非bug情况下使用异常(特别是如果他们来自Java),例如,如果您的网络连接在某些使用套接字的代码中深入,则要展开堆栈.知道为什么游戏选择不这样做会很有趣,即使答案只是"因为我们不像某些其他C++程序员,更喜欢返回错误代码而不是抛出异常". (6认同)
  • 好点.给出诚实答案的+1,也因为它来自有写游戏经验的人 (4认同)
  • 如果你问我,你的网络连接丢失是特殊行为,并且完全有效地使用例外.它不像你期望这种情况发生在游戏的每一帧中,大多数优秀的编译器都没有速度开销,除非抛出异常. (4认同)
  • 来吧.网络连接丢弃不是一种例外行为.这是正常的. (3认同)
  • 从代码的角度来看,它是.正常行为是连接正常并正常工作. (2认同)

Cha*_*tin 56

看,你听到的大多数关于编程效率的都说是神奇的思考和迷信.智能指针确实有性能成本; 特别是如果你在内循环中做了很多花哨的指针操作,它可能会产生影响.

也许.

但是当人们说出这样的话时,通常是有人在很久以前告诉他们X是真的,没有任何东西,只有直觉背后的结果.现在,细胞/多态性问题听起来似乎有道理 - 我打赌这对第一个说出来的人做了.但我还没有证实.

你会听到关于操作系统的C++的相同内容:它太慢了,它会做你想要做得好的事情,非常糟糕.

尽管我们完全用C++构建OS/400(从v3r6开始),裸机开启,并且获得了快速,高效和小型的代码库.这需要一些工作; 特别是从裸机工作,有一些自举问题,使用新的安置,这种事情.

C++可能是一个问题,因为它太大了:我现在正在重读Stroustrup的手腕,它非常令人生畏.但我不认为有任何固有的东西表明你不能在游戏编程中有效地使用C++.

  • 相当多的工程师说C++不适用于嵌入式系统.我作为嵌入式系统工程师工作,尽可能使用C++,结果是更高质量的代码,更紧凑,更易于维护,并且需要更少的调试.可能大多数相信这种情况的人都是那些认为C++ C有一些扩展的人.我还没有看到速度问题的代码,因为它是用C++而不是C编写的.而且,现代C++编译器做得非常好,结果通常和C一样高效. (9认同)
  • C++委员会在嵌入式系统中编写了关于C++使用的TR(技术报告)是有原因的 - 有太多的神话.TR可从ISO C++网站免费获得. (9认同)
  • 我敢打赌,这是第一次有人在同一句话中说出"游戏"和"OS/400"这两个词.感谢写这么好的操作系统. (8认同)
  • 游戏开发者拥有非常好的性能分析工具.所以通常当我听到有人说智能指针如何在取消引用或虚拟调用上花费大量时间时,这是因为他们在他们的一个分析工具中看到了它.我们使用嵌入式平台,他们都有自己的性能特性,所以我觉得有点冒犯你认为我们都是在你不熟悉我们使用的平台时根据神话和假设来优化我们的游戏. (6认同)
  • +1是一个非常好的答案.唯一要添加的是在有些事情要慢的情况下进行分析,然后解决通过分析找到的"真正的"罪魁祸首.让我们不要忘记Don Knuth告诉我们的事情:"我们应该忘记效率低下,大约97%的时间说:过早的优化是所有邪恶的根源." (3认同)

小智 12

如果您或您的朋友对性能非常偏执,那么请阅读英特尔关于优化的手册.乐趣.

否则,每次都要求正确性,可靠性和可维护性.我宁愿玩一个比崩溃的游戏慢一点的游戏.如果/当您注意到性能问题时,PROFILE然后进行优化.您可能会发现有一些热点代码可以通过使用更高效的数据结构或算法来提高效率.当分析显示它们是获得有价值的加速的唯一方式时,只关心这些愚蠢的小微博优化.

所以:

  1. 编写代码清晰正确
  2. 轮廓
  3. 轮廓
  4. 您可以使用更有效的数据结构或算法来加速瓶颈吗?
  5. 使用微优化作为最后的手段,只有在分析显示它会有所帮助的地方

PS:许多现代C++编译器提供了一种异常处理机制,它增加了零执行开销,除非抛出异常.也就是说,只有在实际抛出异常时才会降低性能.只要异常仅用于特殊情况,那么没有充分的理由不使用它们.


Dav*_*one 7

我在StackOverflow上看到了一篇帖子(我似乎找不到了,所以也许它没有在这里发布),它查看了异常与错误代码的相对成本.人们经常会看到"带有异常的代码"与"没有错误处理的代码",这不是一个公平的比较.如果您要使用异常,那么不使用它们就必须使用其他东西来实现相同的功能,而其他东西通常是错误返回代码.他们发现,即使在一个简单的示例中,单个函数调用级别(因此不需要在调用堆栈中传播异常),在错误情况发生的情况下,异常比错误代码快0.1% - 0.01%的时间或更少,而错误代码在相反的情况下更快.

与上述关于测量异常与无错误处理的投诉类似,人们在虚拟函数方面更经常地进行推理时会出现这种错误.就像你不使用异常作为从函数返回动态类型的方法(是的,我知道,你的所有代码都是例外),你不会使函数成为虚拟函数,因为你喜欢它在语法中看起来的样子荧光笔.您将函数设置为虚拟,因为您需要特定类型的行为,因此您不能说虚拟化很慢,除非您将其与具有相同操作的内容进行比较,并且通常替换是许多switch语句或大量代码复制.那些也有性能和内存命中.

至于游戏没有错误和其他软件的评论,我可以说的是,我显然没有玩过他们的软件公司制作的任何游戏.我在Pokemon的精英4的地板上冲浪,被困在Oblivion的一座山内,被Gloams杀死,意外地将他们的法力伤害与他们的hp伤害结合起来,而不是在暗黑破坏神II中单独进行,并推动自己通过一个封闭的大门与一块巨大的岩石在暮光公主中与一只鸟和一个弹弓打击哥布林.软件有bug.使用异常不会使无错误的软件错误.

标准库的异常机制有两种类型的异常:std::runtime_errorstd::logic_error.我可以看到不想使用std::logic_error(我用它作为临时的东西来帮助我测试,目标是最终删除它,而且我还把它留作永久性检查).std::runtime_error然而,这不是一个错误.我抛出一个例外,std::runtime_error如果我连接的服务器发送给我的是无效数据(安全编程的规则#1:信任没有人,甚至是你认为你写的服务器),例如声称他们正在向我发送消息12个字节,然后他们实际上发送给我15.在这种情况下,只有两种可能性:

1)我连接到恶意服务器,或

2)我与服务器的连接已损坏.

在这两种情况下,我的反应都是一样的:断开连接(无论我在代码中的哪个位置,因为我的析构函数会为我清理),等待几秒钟,然后再次尝试连接到服务器.我做不了什么.我绝对可以给出所有错误代码(这意味着通过引用传递其他所有东西,这是一个性能损失,并严重混乱代码),或者我可以抛出一个异常,我在我的代码中找到哪个我确定哪些服务器连接到(在我的代码中可能会非常高).

我在代码中提到了什么错误?我不这么认为; 我认为它接受我必须与之交互的所有其他代码都是不完美或恶意的,并且确保我的代码在面对这种模糊性时仍然保持高效.

对于智能指针,再次,您尝试实现的功能是什么?如果您需要智能指针的功能,那么不使用智能指针意味着手动重写其功能.我认为很明显为什么这是一个坏主意.但是,我很少在自己的代码中使用智能指针.我真正做的唯一一次是,如果我需要在标准容器中存储一些多态类(比如,基于战斗类型派生的某个基类std::map<BattleIds, Battles>在哪里Battles),在这种情况下我用了一个std::unique_ptr.我相信有一次我std::unique_ptr在一个类中使用了一些库代码.我使用的大部分时间都是std::unique_ptr制作不可复制的,不可移动的类型.但是,在许多情况下你会使用智能指针,只是在堆栈上创建对象并从方程中完全删除指针似乎更好.

在我的个人编码中,我并没有真正发现许多情况,其中代码的"C"版本比"C++"版本更快.事实上,它通常是相反的.例如,考虑的许多例子std::sort对比qsort(由Bjarne Stroustrup的使用常见的例子),其中std::sort则会覆盖qsort,或者我最近比较std::copymemcpy,其中std::copy居然有一个轻微的性能优势.

太多的"C++特征X太慢"声称似乎是基于比较它没有功能.性能最高(在速度和内存方面)和无错误的代码是int main() {},但我们编写程序来做事情.如果您需要特定的功能,那么不使用为您提供该功能的语言功能将是愚蠢的.但是,您应该首先考虑您希望程序执行的操作,然后找到执行此操作的最佳方法.显然你不想以"我想编写一个使用C++特性X的程序"开头,你想要开始"我想编写一个很酷的东西Z的程序",也许你最终会在". ..并且实现的最佳方式是特征X".


Ada*_*icz 6

很多人对事物做出绝对的陈述,因为他们实际上并没有思考.他们宁愿只是应用规则,使事情变得更乏味,但需要更少的设计和预见.我现在宁愿做一些艰难的思考,然后当我做一些毛茸茸的事情,并抽象掉那些乏味的东西,但我想不是每个人都这么想.当然,智能指针具有性能成本.做例外.这只是意味着您的代码可能会有一部分不应该使用它们.但是你应该首先介绍并确保问题实际上是什么.

免责声明:我从未做过任何游戏编程.


Ada*_*eld 5

关于Cell架构:它具有不连贯的缓存.每个SPE都有自己的256 KB本地存储.SPE只能访问这个内存; 必须使用DMA访问任何其他内存,例如512 MB主内存或另一个SPE的本地存储.您可以手动执行DMA,并通过显式启动DMA传输将内存复制到本地存储中.这使同步成为一个巨大的痛苦.

或者,您实际上可以访问其他内存.主内存和每个SPE的本地存储都映射到64位虚拟地址空间的某个部分.如果通过正确的指针访问数据,DMA就会在幕后发生,而且它们看起来都像是一个巨大的共享内存空间.问题?巨大的表现受到打击.每次访问其中一个指针时,SPE都会在DMA发生时停止.这很慢,而且你不想在性能关键代码(即游戏)中做些什么.

这让我们看到了Skizz关于vtable和指针修复的观点.如果你盲目照搬的SPE之间围绕虚表指针,你会招致巨大的性能损失,如果你不修理你的指针,而你也将招致巨大的性能损失,如果你修理你指针并将虚拟功能代码下载到SPE.


Dan*_*son 5

我在索尼的一篇精彩演讲中称之为"面向对象编程的陷阱".这一代控制台硬件确实让很多人重新审视了C++的OO方面,并开始询问它是否真的是最好的前进方式.

你可以找到展示这里(直接链接在此).也许你会发现这个例子有点人为,但希望你会发现这种对高度抽象的面向对象设计的厌恶并不总是基于神话和迷信.