C:使用大量结构体会使程序变慢吗?

5
我正在编写一个打砖块游戏的克隆版本。我曾经有一个版本,其中只有一层结构。这个版本的运行速度为70帧每秒。
为了使代码更加清晰,我决定代码应该有更多的抽象,并创建了更多的结构体。大多数情况下,我有两到三层深的结构体。这个版本的运行速度为30帧每秒。
由于结构体之外还有其他差异,所以我想问:在C语言中使用很多结构体会显著降低代码的运行速度吗?
例如,在第二个版本中,我使用了:
struct Breakout
{
   Ball ball;
   Paddle paddle;
   Level* levels;
}

struct Level
{
   Bricks* bricks;
}

所以,我经常使用breakout.levels[level_in_play].bricks[i].visible这样的语句。这可能是问题的原因吗?

谢谢。


结构体是否由于值或引用而“深层”?您是否进行动态内存分配? - WhirlWind
1
如果您在性能敏感的路径中动态分配内存,那肯定会使程序变慢。我建议对两个代码版本进行快速分析,以了解原因。但是,您需要展示一些代码,而不仅仅是一些数据结构。 - WhirlWind
“我可以运行一个快速的分析吗?”这是什么意思?使用malloc根据砖块数量进行内存分配比使用Bricks bricks [MAX_NUMBER_BRICKS]建立最大值要慢吗?谢谢。 - nunos
1
WhirlWind 是正确的。对编译后的代码进行性能分析。我建议先只对你的“struct”版本进行分析,看看是否能够发现问题,然后再使用“非结构体”版本进行第二次分析,以查看性能变化的位置。我猜测问题可能出在你的代码中的其他地方,或者与重复在错误的位置分配过多内存(或复制对象、按值传递等)有关。 - Allbite
1
你是怎么在一个打砖块游戏中遇到性能问题的? - Trillian
4个回答

12
大量使用指针解引用可能会影响性能。当你将一个大结构体拆分成小结构体时,会发生两件事情:
  1. 访问子结构体成员需要额外的指针解引用和内存获取,稍微慢一些;
  2. 可能会降低局部性原理,导致更多的缓存未命中和页面故障,严重降低性能。
局部性原则可能是你这里遇到的问题。如果可能,尽量在同一个malloc块中分配相关结构体,这会增加它们被一起缓存的可能性。

你是否曾经发现指针解引用是性能问题的根源?当然,它会导致微小的、几乎无限小的额外工作量,但我认为除非你对每个操作都进行了数十亿次的解引用,否则指针解引用永远不会成为问题。优化过早可能会导致性能下降,唯一的方法是进行分析。 - Allbite
@Allbite:显然我没有表达清楚。它不是解引用可能导致性能损失(至少不太可能——我确定有一些边缘情况很重要),而是你可能会将密切相关的数据放在内存中的不同位置。当然,对应用程序进行分析很重要,但是参考局部性问题可能很难通过运行分析器来发现,因为分析器已经通过附加影响了性能和参考局部性。 - Daniel Pryden

4
首先,猜测问题是容易且有诱惑力的。猜测的麻烦之处在于 - 有时候它们是正确的。但既然可以找出确切的问题所在,为什么还要猜测呢?我推荐这种方法。 话虽如此,我猜测可能是 mallocfree 在汇编语言级别上单步执行时会做更多的事情。如果我知道我不会经常分配结构体的内存,我只会为结构体分配内存。如果我必须以高频率动态分配/释放它们,则有一个已使用副本的空闲列表会有所帮助,这样我就可以从列表中获取它们而不是一直调用 malloc
尽管如此,请获取一些栈快照。很有可能您可以解决一系列问题并使其运行速度更快。

4
添加额外的解引用层可能会导致轻微的(非常轻微的)性能损耗。原因是编译器看到每个 -> 都意味着它必须进行额外的内存查找和偏移量计算。例如,c->b->a 需要将指针 c 加载到内存中,引用它,偏移至 b,解引用它,偏移至 a,然后解引用它,最后将 a 加载到内存中。 这需要相当多的内存工作。而 c.b.a 只需要加载 c,一个加法操作,然后直接从内存中加载 a。这是 2 次加载与 5 次加载的区别。
除非在小而紧密的循环中大量执行此类操作,否则不会对时间造成影响。如果您正在重度内部循环中执行此操作(且编译器没有帮助您),那么它可能会累积起来。对于这些情况,请考虑缓存最低级别的结构体指针并从那里开始操作。
话虽如此,任何时候提到性能,第一步都是进行分析。没有分析,您就在瞎猜。您已经声明了结构解引用是性能问题的根源,但是如果没有最新的有效分析(在发布版本上),那么您就是在瞎猜,可能会浪费时间。

+1 - 根据我的经验,结构体引起大幅度的减速是不太可能的,但也不是完全不可能。请进行性能分析。 - Steve Rowe

1

在编程中,不是“大量结构体”本身会导致问题,而是可能会增加内存访问的次数。然而,“大量间接引用”更有可能是问题的原因。您必须考虑到达实际数据需要生成多少内存访问。数据的接近性和大小也可能影响缓存,但这更难分析。

另外,由于您在评论中提到正在执行动态内存分配,因此查找和分配块所需的时间是不确定和可变的。如果您在算法执行期间重复分配和释放块(而不是例如在初始化时进行预分配),这可能会导致性能下降和变化。

如果您有性能分析工具,请对代码进行分析以查看性能损失发生的位置。


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