你用什么编码技术来优化C程序?

几年前,我在一个面板上,正在面试相对高级的嵌入式C程序员职位的候选人。

我问的一个标准问题是关于优化技术。 我很惊讶有些候选人没有答案。

因此,为了为后人列出一份清单 - 在优化C程序时通常使用哪些技术和构造?

速度和尺寸优化的答案都可以接受。


首先要做的事 - 不要太早优化。 花时间仔细优化一小段代码,发现它不是您认为它将会出现的瓶颈并不罕见。 或者换句话说,“在你快速制作之前,让它工作”

调查在优化代码之前是否有任何优化算法的选项。 通过优化一个糟糕的算法,而不是优化代码会更容易找到性能的改进,只有当您改变算法时才会抛弃它。

并找出你为什么需要首先进行优化。 你想达到什么目的? 如果您正试图改进某些事件的响应时间,那么是否有机会更改执行顺序以最小化时间关键区域。 例如,当试图改善对某些外部中断的响应时,可以在事件之间的死亡时间内做任何准备工作?

一旦你决定你需要优化代码,你优化哪个位? 使用分析器。 集中注意力(第一)在最常用的区域。

那么你可以对这些领域做些什么?

  • 减少条件检查。 检查条件(例如循环的终止条件)是没有花费在实际处理上的时间。 条件检查可以通过循环展开等技术来最小化。
  • 在某些情况下,条件检查也可以通过使用函数指针来消除。 例如,如果你正在实现一个状态机,你可能会发现将各个状态的处理程序实现为小函数(使用统一的原型)并通过存储下一个处理程序的函数指针来存储“下一个状态”比使用在单个case语句中实现的具有处理程序代码的大型switch语句。 因人而异。
  • 尽量减少函数调用 函数调用通常会带来上下文保存的负担(例如,将寄存器中包含的局部变量写入堆栈,保存堆栈指针),因此如果不必拨打电话,则可节省时间。 一种选择(如果你正在优化速度而不是空间)是利用内联函数。
  • 如果函数调用不可避免地最小化传递给函数的数据。 例如,传递指针可能比传递结构更有效率。
  • 在为速度进行优化时,请选择适合您平台的本机大小的数据类型。 例如,在32位处理器上,操作32位值可能比8位或16位值更有效。 (注意 - 值得检查一下编译器是否按照你的想法进行了检查,我发现我的编译器坚持要对8位值进行16位算术运算,并将所有的转换和转换与他们一起去)
  • 查找可以预先计算的数据,并且可以在初始化期间计算,也可以在编译时计算(更好)。 例如,在实现CRC时,您可以即时计算CRC值(使用直接使用多项式),这对于大小很大(但性能可怕),或者可以生成一个包含所有临时值的表格 - 这是一个执行速度要快得多,对规模不利。
  • 本地化您的数据。 如果你经常操纵一个数据块,你的处理器可以通过将其全部存储在缓存中来加快速度。 您的编译器可以使用适合更多本地化数据的较短指令(例如,使用8位偏移量而不是32位的指令)
  • 同样,本地化您的功能。 出于同样的原因。
  • 弄清楚你可以对你正在执行的操作做出的假设,并找出利用它们的方法。 例如,在8位平台上,如果您对32位值进行的唯一操作是增量,则可能会发现通过专门为此目的内联(或创建宏)可以比编译器做得更好,而不是使用正常的算术运算。
  • 避免昂贵的指示 - 分部是一个很好的例子。
  • “注册”关键字可以是你的朋友(虽然希望你的编译器对你的注册使用情况有很好的想法)。 如果你打算使用“注册”,很可能你必须声明你想先注册的局部变量。
  • 与您的数据类型保持一致。 如果您对数据类型混合使用算术(例如,shorts和ints,double和float),那么编译器会为每个不匹配添加隐式类型转换。 这是浪费的CPU周期,可能不是必需的。
  • 上面列出的大多数选项都可以用作正常练习的一部分,没有任何不良影响。 但是,如果你真的试图取得最佳性能: - 调查你可以安全地禁用错误检查。 这不是建议的,但它会为你节省一些空间和周期。 - 在汇编程序中手工编写代码的一部分。 这当然意味着你的代码不再是可移植的,但是这不是一个问题,你可能会在这里找到节省。 请注意,尽管可能会有时间丢失数据移入和移出寄存器的数据(即,为了满足您编译器的寄存器使用情况)。 另外请注意,你的编译器本身应该做的很好。 (当然,也有例外)


    正如其他人所说: 简介,简介简介。

    至于实际的技术,我认为还没有提到:

    冷热数据分离 :保持在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个字节,因此您可以在缓存中容纳更多条目。 如果可以将其应用于渲染引擎的内部环境,则这可能是一个巨大的胜利。


    我最喜欢的技术是使用一个好的分析器。 没有一个好的配置文件告诉你瓶颈在哪里,没有任何技巧和技巧可以帮助你。

    链接地址: http://www.djcxy.com/p/86759.html

    上一篇: What coding techniques do you use for optimising C programs?

    下一篇: Performance issue: Java vs C++