几年前,我参加了一个小组讨论,该小组正在面试一位相对高级的嵌入式C程序员职位的候选人。
我问过的一个标准问题是优化技术。我很惊讶有些候选人没有答案。
因此,为了为后代制作一个列表,您在优化C程序时通常会使用哪些技术和结构?
接受优化速度和尺寸的答案。
答案 0 :(得分:37)
答案 1 :(得分:26)
正如其他人所说:个人资料,个人资料个人资料。
至于实际技术,我还没有提到过:
Hot&冷数据分离:保持在CPU的缓存中非常重要。一种帮助实现此目的的方法是将数据结构拆分为频繁访问(“热”)并且很少访问(“寒冷的“)部分。
一个例子:假设您有一个类似于此的客户结构:
struct Customer
{
int ID;
int AccountNumber;
char Name[128];
char Address[256];
};
Customer customers[1000];
现在,我们假设你想要访问ID和AccountNumber很多,但不是那么多名称和地址。你要做的是把它分成两部分:
struct CustomerAccount
{
int ID;
int AccountNumber;
CustomerData *pData;
};
struct CustomerData
{
char Name[128];
char Address[256];
};
CustomerAccount customers[1000];
通过这种方式,当您循环遍历“customers”数组时,每个条目只有12个字节,因此您可以在缓存中容纳更多条目。如果您可以将它应用于渲染引擎的内部循环等情况,那么这可能是一个巨大的胜利。
答案 2 :(得分:20)
我最喜欢的技巧是使用一个好的探查器。如果没有一个好的概况告诉你瓶颈在哪里,没有任何技巧和技巧可以帮助你。
答案 3 :(得分:15)
我遇到的最常见的技术是:
至于一般性建议,其中大多数已经响起:
答案 4 :(得分:8)
对于低级优化:
答案 5 :(得分:5)
答案 6 :(得分:4)
如果可能的话,比较0,而不是任意数字,特别是在循环中,因为与0的比较通常使用单独的,更快的汇编程序命令来实现。
例如,如果可能,写下
for (i=n; i!=0; --i) { ... }
而不是
for (i=0; i!=n; ++i) { ... }
答案 7 :(得分:4)
由于我的应用程序通常不需要太多的CPU时间,我会专注于磁盘和内存中二进制文件的大小。我所做的主要是寻找静态大小的数组,并用动态分配的内存替换它们,这值得额外的努力,以后释放内存。为了减少二进制文件的大小,我寻找在编译时初始化的大数组,并将初始化放入运行时。
char buf[1024] = { 0, };
/* becomes: */
char buf[1024];
memset(buf, 0, sizeof(buf));
这将从二进制文件.DATA部分中删除1024个零字节,而是在运行时在堆栈上创建缓冲区,并用零填充它。
编辑:哦,是的,我喜欢缓存一些东西。它不是C特定的,但取决于你的缓存,它可以给你一个巨大的性能提升。PS:请在列表完成时告诉我们,我很好奇。 ;)
答案 8 :(得分:4)
预成熟优化是万恶之源! ;)
答案 9 :(得分:4)
避免使用堆。对相同大小的对象使用obstacks或pool-allocator。把寿命短的小东西放到堆栈上。 alloca仍然存在。
答案 10 :(得分:3)
另一件未提及的事情是:
答案 11 :(得分:3)
现在,优化中最重要的事情是:
不要为涉及复制和粘贴代码的优化(如循环展开)或手动重新排序循环而烦恼。编译器通常比你这样做做得更好,但是大多数都不够聪明,无法撤消它。
答案 12 :(得分:3)
基础/一般:
一些实际上有帮助的事情:
选择尺寸/内存:
选择速度(小心):
答案 13 :(得分:3)
难以总结......
数据结构:
算法:
低级别:
最重要的是:尽早测量,经常测量,永远不做假设,根据探查器检索的数据进行思考和优化(请使用PTU)。
另一个提示,性能是应用程序成功的关键,应该在设计时考虑,你应该有明确的性能目标。
这远非详尽无遗,但应该提供一个有趣的基础。
答案 14 :(得分:2)
如果有人对这个问题没有答案,可能是他们不太了解。
也可能是他们知道很多。我知道很多(恕我直言:-),如果我被问到这个问题,我会问你回答:为什么你认为这很重要?
问题在于,如果没有特定情况通知,任何关于绩效的先验概念都是根据定义猜测的。
我认为了解性能编码技术很重要,但我认为知道不使用它们更为重要,直到诊断显示存在问题及其原因。
现在我要反驳自己,并说,如果你这样做,你就会学会如何识别导致麻烦的设计方法,以便你可以避免它们,对新手来说,这听起来像是过早优化。
为您举一个具体的例子,this is a C application that was optimized。
答案 15 :(得分:2)
有时你必须决定你所追求的是更多的空间还是更高的速度,这将导致几乎相反的优化。例如,为了充分利用您的空间,您需要pack个结构,例如#pragma pack(1)并在结构中使用bit fields。为了提高速度,您需要打包以与处理器首选项保持一致并避免使用位域。
另一个技巧是通过realloc为增长数组选择正确的重新调整大小的算法,或者更好地根据您的特定应用程序编写自己的堆管理器。不要认为编译器附带的那个是每个应用程序的最佳解决方案。
答案 16 :(得分:2)
在我工作的大部分嵌入式系统上都没有分析工具,所以很高兴说使用分析器但不太实用。
速度优化的第一条规则是 - 找到您的关键路径 通常你会发现这条路不是那么长而且不那么复杂。通用方式很难说如何优化它取决于你在做什么以及你有什么能力去做。例如,您通常希望在关键路径上避免使用memcpy,因此您需要使用DMA或优化,但如果您没有DMA,那该怎么办?检查memcpy实现是否是最好的,如果不重写它 在嵌入式设备中根本不使用动态分配,但如果由于某种原因不在关键路径中这样做 正确地组织你的线程优先级,正确的是真正的问题,它显然是系统特定的 我们使用非常简单的工具来分析瓶颈,存储时间戳和索引的简单宏。很少(2-3)在90%的情况下运行会找到你花费时间的地方 最后一个是代码审核非常重要的一个。在大多数情况下,我们在代码审查期间避免性能问题非常有效:):
答案 17 :(得分:2)
此外,您应该衡量绩效。
答案 18 :(得分:2)
收集代码执行的配置文件可以获得50%的代码。另外50%涉及分析这些报告。
此外,如果您使用GCC或VisualC ++,您可以使用“配置文件引导优化”,其中编译器将从先前的执行中获取信息并重新安排指令以使CPU更快乐。
答案 19 :(得分:2)
内联函数!受到粉丝们的启发,我在这里描述了我的一个应用程序,并发现了一个小功能,可以在MP3帧上进行一些位移。它在我的applcation中占据了所有函数调用的大约90%,因此我将其设置为内联和瞧 - 该程序现在占用了之前的一半CPU时间。
答案 20 :(得分:1)
很棒的名单。我将在上面的列表中添加一个我没有看到的提示,在某些情况下可以以最小的成本产生巨大的优化。
绕过链接器
如果你有一些应用程序分为两个文件,比如main.c和lib.c,在很多情况下你可以在main.c中添加一个\#include "lib.c"
那将完全绕过链接器并允许更多高效优化编译器。
可以实现优化文件之间依赖关系的相同效果,但更改的成本通常更高。
答案 21 :(得分:1)
有时Google是最好的算法优化工具。当我遇到一个复杂的问题时,一些搜索结果显示一些有博士学位的人已经找到了这个和一个众所周知的问题之间的映射,并且已经完成了大部分工作。
答案 22 :(得分:0)
我建议使用更高效的算法进行优化,而不是作为事后的想法,但从一开始就按照这种方式进行编码。让编译器弄清楚小事情的细节,因为它比你更了解目标处理器。
首先,我很少使用循环查找内容,我将项添加到哈希表中,然后使用哈希表查找结果。
例如,您有一个要查找的字符串,然后是50个可能的值。因此,不是执行50次strcmps,而是将所有50个字符串添加到哈希表中,并为每个字符串分配一个唯一的数字(您只需要执行一次)。然后你在哈希表中查找目标字符串,并有一个包含所有50个案例的大型开关(或者有函数指针)。
当使用常见的输入集(例如css规则)查找内容时,我使用快速代码来跟踪唯一可能的搜索,然后迭代思考那些以找到匹配项。一旦我有匹配,我将结果保存到哈希表(作为缓存),然后使用缓存结果,如果我稍后得到相同的输入集。
我提供更快代码的主要工具是:
哈希表 - 用于快速查找和缓存结果
qsort - 这是我使用的唯一一种
bsp - 用于根据区域(地图渲染等)查找内容