5

我正在寻找程序员在 C 中可以做什么,这可以确定生成的目标文件的性能和/或大小。

例如,
1. 将简单的 get/set 函数声明为内联可能会提高性能(以更大的占用空间为代价)
2. 对于不使用循环变量本身的值的循环,倒数到零而不是数到某个值等

看起来编译器现在已经发展到根本不需要“简单”技巧(如上面两点)的水平。Appropriate options during compilation do the job anyway. 哎呀,我还在这里看到了关于编译器如何处理递归的帖子——这非常有趣!那么我们在 C 级别还剩下什么要做呢?:)

我的具体环境是:GCC 4.3.3 re-targeted for ARM architecture (v4)。但是也欢迎对其他编译器/处理器的响应,并将被咀嚼。

PS:我的这种方法违背了通常的“代码优先!,然后是基准测试,最后是优化”的方法。

编辑:就像它发生的那样,我在发布问题后发现了一个类似的帖子:我们是否仍然应该“在小范围内”进行优化?

4

6 回答 6

6

我能想到的编译器可能不会优化的一件事是“缓存友好性”:如果您以行优先顺序迭代二维数组,例如,请确保您的内部循环跨列索引运行以避免缓存抖动。让内部循环在错误的索引上运行可能会导致巨大的性能损失。

这适用于所有编程语言,但如果您使用 C 进行编程,性能可能对您至关重要,因此它尤其重要。

于 2009-07-15T09:21:06.677 回答
5

“总是”知道算法的时间和空间复杂度。编译器将永远无法像您一样完成这项工作。:)

于 2009-07-15T09:21:17.597 回答
3

如今的编译器仍然不太擅长向量化代码,因此您仍然希望自己完成大多数算法的 SIMD 实现。

为您的确切问题选择正确的数据结构可以显着提高性能(在这种特定情况下,我已经看到从 Kd-tree 移动到 BVH 会做到这一点的情况)。

编译器可能会填充一些结构/变量以适应缓存,但其他缓存优化(例如数据的局部性)仍然取决于您。

编译器仍然不会自动使您的代码成为多线程代码,并且根据我的经验,使用 openmp 并没有太大帮助。(无论如何,您确实必须了解 openmp 才能显着提高性能)。所以目前,你自己在做多线程。

于 2009-07-15T09:30:40.807 回答
2

补充一下 Martin 上面所说的关于缓存友好性的内容:

  • 重新排序您的结构,使通常一起访问的字段位于同一缓存行中会有所帮助(例如,仅加载一个缓存行而不是两个缓存行。)通过这样做,您实际上是在增加数据缓存中有用数据的密度。有一个 linux 工具可以帮助您执行此操作:dwarves 1http://www.linuxinsight.com/files/ols2007/melo-reprint.pdf

  • 您可以使用类似的策略来增加代码的密度。在 gcc 中,您可以使用可能/不太可能的标签标记热分支和冷分支。这使 gcc 能够单独保留冷分支,这有助于增加 icache 密度。

现在来点完全不同的东西:

  • 对于可能跨 CPU 访问(读取写入)的字段,相反的策略是有意义的。问题在于,出于一致性目的,只能允许一个 CPU 写入同一个地址(实际上是同一个高速缓存行)。这可能导致一种称为高速缓存行乒乓的情况。这很糟糕,如果该缓存行包含其他不相关的数据,情况可能会更糟。在这里,将这个争用数据填充到缓存行长度是有意义的。

注意:这些显然是微优化,仅在您尝试从代码中提取最后一点性能时才进行。

于 2009-07-15T11:45:41.750 回答
1

在可能的情况下进行预计算...(抱歉,但这并不总是可能的...我在国际象棋引擎上进行了广泛的预计算。)将这些结果存储在内存中,并牢记缓存..内存中预计算数据的大小越小进行缓存命中的机会。由于大多数最近的硬件都是多核的,您可以设计您的应用程序以它为目标。

如果您使用多个大数组,请确保将它们分组在它们将被使用的位置上,从而提高缓存命中率

于 2009-07-15T09:28:37.037 回答
0

许多人没有意识到这一点:定义一个内联标签(因编译器而异),这意味着内联,在其意图中 - 许多编译器将关键字放置在与原始含义完全不同的上下文中。还有一些方法可以在编译器开始将琐碎的事情弹出之前增加内联大小限制。人工内联可以生成更快的代码(编译器通常比较保守,或者没有考虑足够多的程序),但是您需要学习正确使用它,因为它可能(很容易)适得其反。是的,这绝对适用于代码大小和速度。

于 2009-11-17T14:03:48.507 回答