我是否过早优化?


9

我目前处于C ++中基于组件的体系结构的设计阶段。

我当前的设计包括以下功能的使用:

  • std::vector的,共std::shared_ptrs个以容纳组件
  • std::dynamic_pointer_cast
  • std::unordered_map<std::string,[yada]>

组件将代表类游戏软件所需的各种项目的数据和逻辑,例如图形,物理,AI,音频等。

我到处都读到高速缓存未命中会影响性能,因此我进行了一些测试,这使我相信确实会降低应用程序的速度。

我还无法测试上述语言功能,但据说在许多地方,这些功能会花费很多,因此应尽可能避免。

由于我正处于架构的设计阶段,并且这些将包含在设计的核心中,因此,现在我应该设法设法避免使用它们,因为如果有性能,以后很难对其进行更改问题?

或者我只是在做过早的优化而陷入困境?


3
我将不愿意选择一个设计,该设计使以后很难更改,而无论性能问题如何。如果可以,请避免这样做。有许多设计既灵活又快速。
candied_orange

1
甚至不知道细节,这个问题的答案几乎总是响亮的“是!”。
Mawg说恢复Monica

2
@Mawg“ ...但是,我们不应该放弃这3%的临界机会。” 既然这是设计的核心,我怎么知道我是否在进行这3%的工作?
Vaillancourt's

1
一个出色的点,亚历山大(+1),是的,我确实知道引号的后半部分几乎从未被提及:-)但是,在此之前,请回到我的评论中(这反映在简短的回答中) ,the answer to this question is almost always a resounding "YES !!"。我仍然觉得最好先运行然后再进行优化,但是YMMV,每个人都有他的观点,所有观点都是有效的,只有OP才能真正回答他自己的主观问题。
Mawg说恢复Monica

1
@AlexandreVaillancourt继续阅读Knuth的论文(PDF,引文来自PDF阅读器中标有268页,第8页的右侧)。“ ...明智地仔细阅读关键代码;但只有确定了关键代码之后,他才是明智的选择。对程序的哪些部分真正关键进行先验判断通常是一个错误,因为一直在使用测量工具的程序员一直认为他们的直觉猜测会失败。” (强调他的意思)
8bittree '16

Answers:


26

除了标题,什么都没有读:是的。

阅读文字后:是。虽然这事实,地图和共享指针等效果不理想的高速缓存,明智的,你肯定会发现,你想使用它们什么-因为据我的理解-是不是瓶颈,也不会在保持或无论数据结构如何,都可以有效地使用缓存。

编写软件来避免最愚蠢的错误,然后进行测试,然后找到瓶颈,然后进行优化!

Fwiw:https://xkcd.com/1691/


3
同意 首先使它正确运行,因为它能以多快的速度运行都无关紧要。并始终记住,最有效的优化并不涉及代码的调整,它们涉及查找其他更有效的算法。
Todd Knarr '16

10
我想指出的是,第一行是正确的,因为优化总是过早的,而是因为只有知道您需要优化才不会过早,在这种情况下,您就不会再询问它了。因此,第一行是正确的,因为您实际上正在问优化是否过早的问题,这意味着您不确定是否需要优化,这从定义上讲还为时过早。ew
约尔格W¯¯米塔格

@JörgWMittag:同意。
steffen

3

我不熟悉C ++,但总的来说要视情况而定。

您无需过早优化隔离算法,就可以轻松优化隔离算法。

但是,您需要获得应用程序的总体设计,以实现所需的关键性能指标。

例如,如果您需要设计一个应用程序以每秒处理数百万个请求,则需要在设计应用程序时考虑该应用程序的可伸缩性,而不是使该应用程序正常运行。


3

如果您必须询问,那么可以。过早的优化意味着在确定存在重大性能问题之前先进行优化。


1

ECS?我实际上建议如果在设计的面向数据的方面投入大量的思想并基准化不同的代表可能还为时过早,因为它可能会影响您的界面设计,而后者在后期进行更改的成本很高。游戏。而且ECS只是需要大量的工作和前期的思考,我认为值得花一些时间来确保它不会给您带来设计级的性能困扰,因为这将成为您的核心整个怪胎引擎。这部分让我大吃一惊:

unordered_map<string,[yada]>

即使使用较小的字符串优化,也可以在另一个可变大小的容器(unordered_maps)内有一个可变大小的容器(字符串)。事实上,小串的优化实际上可能是有害的在这种情况下有益的,如果你的表是很稀疏,因为小串的优化将意味着哈希表的每一个未使用的指标仍然会使用更多的内存用于SS优化(sizeof(string)会更大),以至于哈希表的总内存开销可能会比您存储在其中的开销更大,尤其是如果它是一个简单的组件(例如位置组件),并且由于步幅大而导致更多的缓存丢失从哈希表中的一个条目转到下一个条目。

我假设字符串是某种键,例如组件ID。如果是这样,这已经使事情大大便宜了:

unordered_map<int,[yada]>

...如果您希望拥有脚本编写者可以使用的用户友好名称的好处,例如,实习字符串可以为您提供两全其美的体验。

就是说,如果您可以将字符串映射到合理范围的密集使用的索引,那么您也许可以做到这一点:

vector<[yada]> // the index and key become one and the same

我不认为这种过早的原因是,它又可能会影响您的界面设计。DOD的目的不应该是尝试在一次IMO中提出可以想象得到的最有效的数据表示形式(通常应根据需要迭代实现),而应该对它们进行充分的考虑以设计顶部的接口以与之协同工作。数据为您留出了足够的喘息空间,可以进行分析和优化,而无需级联设计更改。

举一个朴素的例子,一个视频处理软件将所有代码与此相结合:

// Abstract pixel that could be concretely represented by
// RGB, BGR, RGBA, BGRA, 1-bit channels, 8-bit channels, 
// 16-bit channels, 32-bit channels, grayscale, monochrome, 
// etc. pixels.
class IPixel
{
public:
    virtual ~IPixel() {}
    ...
};

是不会得到远没有潜在的史诗重写,因为在一个抽象的想法像素水平已经极其低效(的vptr本身往往会成本比整个像素更多的内存)相比,在提取图像电平(这将通常代表数百万个像素)。因此,请事先对数据表示形式进行足够的考虑,以使您不必面对这样的噩梦场景,理想情况下也不必再面对这种情况,但是在这里,我确实值得对这些东西进行考虑,因为您不想构建一个您的ECS周围有复杂的引擎,发现ECS本身就是瓶颈,要求您在设计级别进行更改。

至于ECS缓存未命中,我认为开发人员经常会努力使ECS缓存友好。它开始产生的冲击力太小,以至于无法以完全连续的方式访问所有组件,并且通常意味着到处都在复制和改组数据。通常,只需要对基元索引进行基数排序就足够了,然后再访问它们,这样您就可以至少不将内存区域加载到高速缓存行中,仅逐出然后加载在同一循环中再次访问访问同一缓存行的不同部分。而且,ECS不必全盘提供惊人的效率。它不像物理系统或渲染系统那样能使输入系统受益,所以我建议目标是“好” 在您真正需要的地方全面提高效率并“出色”。也就是说,使用unordered_mapstring这里有足够容易避免的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.