STL替代品

28

我非常讨厌使用STL容器,因为它们使我的代码的调试版本运行非常缓慢。有没有其他人使用STL以外的东西,在调试构建时具有合理的性能?

我是一名游戏程序员,在我参与的许多项目中都遇到了这个问题。如果你在所有地方都使用STL容器,要达到60帧每秒的速率就相当困难。

我大多数工作都使用MSVC。

15个回答

25

EASTL是一个可能的选择,但仍不完美。电子艺界的Paul Pedriana对各种STL实现在游戏应用程序中的性能进行了调查,其中摘要可在此处找到: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html

这些调整之一正在审核是否将其纳入C++标准。

请注意,即使EASTL也没有针对非优化情况进行优化。我以前有一个带有一些计时的Excel文件,但我想我已经丢失了它,但对于访问来说,它大概是这样的:

       debug   release
STL      100        10
EASTL     10         3
array[i]   3         1

我最成功的经验是自己制作容器。这样可以让容器性能接近于数组[x]。


如果我可以再加一个+1的话,我会这么做。链接http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html非常非常有启发性。 - paercebal
2
假设数组在堆上分配,范围检查被禁用\编译器被设置为优化,我不相信C数组比std::vector具有更快的访问时间。 - Viktor Sehr

21

我的经验是,设计良好的STL代码在调试构建中运行缓慢,因为优化器被关闭了。STL容器会发出许多构造函数和operator=的调用,在发布版本中可以被内联/删除(如果它们是轻量级的)。

此外,Visual C++ 2005及以上版本在发布和调试构建中均启用了STL检查。对于STL重负荷的软件来说,这是一个巨大的性能开销。你可以通过为所有编译单元定义_SECURE_SCL=0来禁用它。请注意,具有不同_SECURE_SCL状态的不同编译单元几乎肯定会导致灾难。

你可以创建第三个构建配置并关闭检查,然后使用该配置进行性能调试。我建议你保留带有检查的调试配置,因为它非常有助于捕捉错误的数组索引等问题。


10

如果您正在使用Visual Studio,可能需要考虑以下内容:

#define _SECURE_SCL 0
#define _HAS_ITERATOR_DEBUGGING 0

这只是针对迭代器的问题,你要执行哪种类型的STL操作?您可以考虑优化您的内存操作;例如,使用resize()一次插入多个元素,而不是使用pop/push一个接一个地插入元素。


4
这些常数是否应该自我解释?它们是用来做什么的? - Andreas Haferburg

7

对于大型、性能关键的应用程序,构建针对您需要的容器可能值得投入时间。

我在谈论这里的是真正的游戏开发。


是的,这实际上是我在所有没有继承代码的游戏中所做的。而且由于您控制分配器,这可能是最好的答案。我只是希望能找到更好的东西! - Tod

4

我敢打赌你的STL在调试时使用了一个检查实现。这可能是一件好事,因为它可以捕捉迭代器超限等问题。如果这对你来说是个大问题,那么可能有一个编译器开关可以关闭它。请查阅文档。


4

3

3

1
我认为EASTL并不对公众开放。但是该文档涵盖了当前STL实现中的许多问题。 - Torlack

3

在调试版本中,MSVC使用了非常重量级的检查迭代器实现,其他人已经讨论过了,所以我不会重复(但可以从那里开始)。

另一件可能对您有兴趣的事情是,您的“调试版本”和“发布版本”可能涉及更改(至少)4个仅松散相关的设置。

  1. 生成.pdb文件(cl /Zi和link /DEBUG),允许进行符号调试。您可能希望将/OPT:ref添加到链接器选项中;链接器在不生成.pdb文件时会删除未引用的函数,但在/DEBUG模式下,它会保留它们所有(因为调试符号引用它们),除非您显式地添加此选项。
  2. 使用C运行时库的调试版本(可能是MSVCR*D.dll,但这取决于您正在使用哪个运行时)。这归结为/MT或/MTd(如果没有使用dll运行时,则为其他内容)
  3. 关闭编译器优化(/Od)
  4. 设置预处理器#define DEBUG或NDEBUG

这些可以独立切换。第一个不会影响运行时性能,但会增加大小。第二个使许多函数变得更加昂贵,但对malloc和free有巨大影响;调试运行时版本会小心地将它们接触的内存“毒化”,以使未初始化的数据错误变得清晰。我相信在MSVCP* STL实现中,它还消除了通常执行的所有分配池,因此泄漏会显示您认为的确切块,而不是它正在子分配的某个更大的内存块;这意味着它会在它们变得更慢的同时进行更多的malloc调用。第三个;好吧,那个做了很多事情(这个问题对该主题进行了一些很好的讨论)。不幸的是,如果您想要单步运行平稳工作,则需要它。第四个以各种方式影响许多库,但最值得注意的是,它编译或消除了assert()和其它相关内容。

因此,您可能考虑创建某些较小组合的生成。我经常使用具有符号(/Zi和link /DEBUG)和断言(/DDEBUG)的生成,但仍然进行了优化(/O1或/O2或您使用的任何标志),但保留堆栈帧指针以进行清晰的回溯(/Oy-)并使用正常运行时库(/MT)。这执行接近我的发布生成,并且是半可调试的(回溯很好,单步运行在源级别上有点奇怪;当然,汇编级别可以正常工作)。您可以拥有任意数量的配置;只需克隆您的发布版本并打开看起来有用的任何调试部分即可。


1

Ultimate++有自己的一组容器 - 不确定您是否可以将它们与库的其余部分分开使用:http://www.ultimatepp.org/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接