代码优化

3

如果我有一个大的结构体(具有许多成员变量),这个结构体指针在我的代码中被传递给许多函数。这个结构体的一些成员变量经常被使用,在几乎所有的函数中都会用到。

  1. 如果我在结构声明中把那些频繁使用的成员变量放在前面,是否会优化MCPS - 每秒百万周期数(代码消耗的时间)?如果我把频繁访问的成员变量放在前面,它们会比随机放置在结构体的底部更高效/使用更少的时间吗?如果是,逻辑是什么?

  2. 如果我在某个函数中访问一个结构体成员变量,如下所示:

    structurepointer1->member_variable

如果我将其分配给一个局部变量,然后再访问局部变量,是否有助于优化MCPS方面呢?

local_variable = structurepointer1->member_variable;

如果是的话,它有什么帮助吗?
8个回答

14

1) 结构体中的一个字段在访问时间上不应受位置影响,除非你的结构体非常大并且跨越多个页面,此时最好将经常连续使用的成员字段放在一起以增加引用的局部性,并尝试减少缓存未命中。

2) 可能/也可能不会。实际上这可能会使事情变慢。如果该变量不是易失性的,则编译器可以聪明地将该字段存储在寄存器中。即使没有,您的处理器也会缓存其值,但是如果使用相距较远且存在许多其他内存访问的情况下,这可能无济于事。如果该值本来就应该存储在寄存器中或者本来就会留在处理器的缓存中,则将其赋值给本地变量只会是不必要的额外工作。

标准优化免责声明:始终在优化之前进行分析。确保您要优化的内容值得优化。始终分析您尝试的优化,并确保它们确实加快了速度(而不是减慢了速度)。


1
在编程中,除了页面考虑外,还要考虑缓存行的因素,但总体而言,我同意。 - user47559

7

首先,必须声明一点:对于所有性能问题,您必须分析代码以查看可以进行哪些改进。

总的来说,任何你可以做的让数据保持在处理器缓存中的操作都会有所帮助。将最常访问的项目放在一起可以促进这一点。


3
我知道这并不是真正回答你的问题,但在深入进行代码超级优化之前,请查看此演示文稿http://dl.fefe.de/optimizer-isec.pdf。我亲眼见过它,这是一个很好的启发式体验,显示编译器在优化方面比我们想象的要先进得多,可读性的代码比小优化更重要。
对于第二个问题,最好不要声明局部变量。编译器通常足够聪明,可以找出变量何时以及如何被使用,并利用寄存器来保留它。
另外,我赞同Mark Ransom的建议,在做出瓶颈假设之前,先对代码进行分析。

那个演示非常棒。我喜欢编译器将 a*9 更改为 a+a*8 的方式。很狡猾。 - Andy Lester

2
我认为您的问题与数据对齐和数据结构填充有关。在现代编译器中,这通常由自动处理,以尝试避免发生内存对齐故障。您可以在此处阅读有关此主题的更多信息。当然,您可以更改数据的对齐方式,但我认为您需要指定一些编译器选项来禁用自动对齐,并重新排列结构中的字段以匹配您所针对的架构。

我认为这是非常低级的优化。


0

是的,它可以有所帮助。但正如其他人已经指出的那样,这取决于情况,甚至可能适得其反。 我认为它有帮助的原因与指针别名有关。如果您通过指针访问变量,并且编译器无法保证结构体没有在其他地方被更改(通过您的指针或其他方式),即使他可以将值保存在寄存器中,他也会生成代码来重新加载或保存变量。以下是一个示例,以说明我的意思:

calc = structurepointer1->member_variable * x + c;    
/* Do something in function which doesn't involve member_variable; */
function(structurepointer1);    

calc2 = structurepointer1->member_variable * y;

编译器将会对成员变量的引用进行内存访问,因为编译器不能确定被调用的函数是否修改了该字段。如果你确定该函数不会改变那个值,则这样做可以节省1次内存访问。
int temp = structurepointer1->member_variable;
calc = temp * x + something;
function(structurepointer1);
calc2 = temp * y;

还有另一个原因可以使用局部变量作为成员变量,那就是它可以使代码更易读。


如果您的函数更改了成员变量,使用临时变量会变得更加有趣。在我上面的示例中,对函数的调用在所有情况下都会在函数调用之前强制进行内存写入,即使它在函数中没有被使用,这比读取访问更为严重。 - Patrick Schlüter

0
  1. 结构体中字段的位置是无关紧要的,因为这将由编译器计算。更有前途的优化是确保您最常用的字段与处理器的字大小字节对齐。

  2. 如果您正在使用函数局部变量,则不应产生影响。如果您将其传递给其他函数(与较大的结构分开),那可能会有所帮助。


(1)并不完全正确。编译器可以改变字段对齐方式并在字段周围添加填充(除非使用特定于编译器的扩展来控制这些内容),但是它不允许重新排序字段。 - Tyler McHenry

0

与其他答案一样,您需要在优化之前运行基准测试以确保更改有效。如果您担心执行时间,请对算法进行分析并进行优化,然后再考虑编译器创建的代码,这样可以更有效地使用资源。

此外,如果您想知道会发生什么,您应该考虑将C代码编译为汇编输出。这将让您了解编译器将要做什么以及如何进一步“微调”。

  1. 结构体访问通常是索引间接访问。汇编代码将有效地提取内存,知道指向结构体的指针作为基础加上索引以获取正确的字段。这通常是一个昂贵的操作,但对于现代CPU来说可能不那么慢。

  2. 这取决于所访问数据的局部性。首先,访问结构体的第一次将是最昂贵的。如果数据已经在处理器寄存器中,则之后访问数据可能很快,但是这可能取决于所使用的处理器。将其存储到本地变量中应该比较便宜,因为此类操作的内存访问指令较少。同样,我认为现在的处理器足够快,这种优化是微不足道的。

我仍然认为可能有更好的地方来优化你的代码。不过,在代码膨胀的世界里,还有人在思考这个问题是很好的事情 ;) 嵌入式计算中,你仍然需要关注这些问题。


0
  1. 这取决于您的字段大小和缓存细节。建议使用valgrind进行分析。
  2. 如果您经常进行解引用操作,它会消耗时间。一个优秀的优化编译器将有效地执行将指针存储到本地变量的优化,就像您所描述的那样。它将比您做得更好,并以特定于架构的方式执行。

在这种情况下,你想要做的是确保测试每个优化的正确性和性能。否则你就是在摸黑。

请记住,在 C 代码层面进行微小的优化几乎永远无法超越高阶算法/设计优化。


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