【问题标题】:Why using pointers (with low optimization) makes the program faster?为什么使用指针(低优化)会使程序更快?
【发布时间】:2019-09-19 14:24:33
【问题描述】:

我正在学习嵌入式 C 编程的教程,然后意识到使用指针指向变量,然后使用它来取消引用可以让程序更快!

我有汇编的基本知识,但我不明白为什么将变量的地址分配给指针会更快,我们不是在谈论按引用或按指针或按值传递!

如我所知,

  • 无指针代码:内存地址已分配给寄存器R0, 就像指针在代码中发生的一样。
  • p_int 成为寄存器R0 的别名,这如何帮助加快程序速度?

不使用指针的代码:

int counter = 0;
int main() {
    while (counter < 6) {
        ++(counter);
    }
    return 0;
}

那么程序集就像

相反,这里是带有指针的代码:

int counter = 0;
int main() {
    int *p;
    p = &counter;
    while (*p < 6) {
        ++(*p);
    }
    return 0;
}

那么程序集就像


更新

我联系了课程创建者,他很乐意为我重播并分解它,为了帮助可能遇到同样问题的其他人,我会留下问题和答案

要访问内存中的变量,CPU 需要这个地址 变量之一的寄存器。在代码的最低级别 优化,编译器从代码内存中加载这个地址 在每次访问变量之前。指针速度 这是因为作为 main() 函数中的局部变量是 分配给一个寄存器。这意味着该地址位于 寄存器(在这种情况下为 R0)并且不需要加载和 每次重新加载到寄存器中。在更高级别的优化 编译器生成更合理的代码和没有 指针与指针一样快。 --彩信

【问题讨论】:

  • 什么编译器?您使用了什么级别的优化?
  • 第一个你要写的 while(counter
  • 是什么让您“意识到”它更快?你是怎么测量的?此外,如果不应用编译器优化,结果在很大程度上是无关紧要的,因为如果性能至关重要,您将使用优化。未优化的代码有利于调试体验而不是性能。
  • 避免使用场外图像,尤其是那些在个人云存储中可能是临时的图像。并且不要像您在此处所做的那样发布 text 的图像。
  • 对调试模式代码进行基准测试毫无意义。在对其进行基准测试或查看之前启用优化。无论如何,如果您仍然想知道为什么一个低效的 asm 循环比另一个低效的 asm 循环之后的细节,请复制/粘贴 asm 输出以使其成为正确的minimal reproducible example。 (在什么硬件上?)

标签: c assembly arm embedded iar


【解决方案1】:

正好相反(几乎相同,但多了一条指令:):

https://godbolt.org/z/xDYecQ

【讨论】:

  • “额外指令”是因为 foo1 更新了 p 的全局值;两个while 循环都编译为同一个程序集。我认为这不是 OP 所要求的(尽管问题不是很清楚);在 OP 代码中 p 是函数的局部变量
  • 感谢您的评论,是的,几乎是一样的,在带有指针的代码中只少了一个 LDR.N
  • @M.M 我知道为什么(你错过了笑脸吗?)
  • @P__J__ 我不明白这是如何回答问题的
  • 为什么要让int*p 成为全球性的? OP的代码将其作为本地代码。这引入了一个令人困惑的不必要差异,因为您的 Godbolt 编译器资源管理器的屏幕截图(!)隐藏了足够多的源窗格,以至于它不是真正可读的。另外,OP 在调试模式下使用不同的编译器为 ARM 编译,就是问题所在。
【解决方案2】:

编译器在优化级别之间变化的任何行为都是特定于实现的。因此,尽管向您展示了一些可能相当违反直觉的东西作为演示,但您不应该被告知这是一种因果关系。

以不同的方式编写代码总是会触发性能改进或回归,而不同的优化级别有时会导致更改以错误的方式进行。这应该是显而易见的,但是任何更高优化级别导致性能更差的情况(本示例并非如此)都应该被视为编译器的问题。

【讨论】:

  • 我认为我之前的标题编辑令人困惑。 OP 的两种情况都禁用了优化,但是引入额外的指针变量实际上是有帮助的,而不是像额外的变量通常那样有害,因为 ARM 无论如何都不能直接访问静态存储;它必须在寄存器中构造一个指针,这与使用本地指针变量非常相似。我重新编辑了标题以避免听起来像启用优化让它变慢了。
  • @PeterCordes,“优化禁用”实际上是否具有正式含义,可以扩展到假设讲师的示例在 10 年后仍会以相同的方式表现?我试图在这里得到的是该示例不支持似乎被教导的内容。如果所教的是这种“在 O0 中引导编译器”是可行的,那实际上听起来会产生误导。
  • 我对“优化禁用”的理解是,每个 C 语句都被编译成一个单独的 asm 块,语句之间的所有变量都在内存中,除了堆栈和帧指针之外,没有任何东西存在于寄存器中。 (因此 vars 可以由调试器修改)。在单个语句中,脑死亡的程度差异很大,例如GCC 仍会将x / 10 优化为乘法逆。 (有趣的事实:register int foo; 实际上将保留在 gcc 的寄存器中;register 关键字实际上在 -O0 上做了一些事情,与其他时间不同。)但公平点,IDK 如果每个人都同意的话
  • 我不认为这个问题是说有什么被专门作为你应该记住的东西。至少我希望不是,因为你指出的原因! OP 说他们注意到(“意识到”)性能差异,并询问了教程的作者。 (我认为暗示这不是教程提出的)。因此,“更新”部分只是对这个 ISA 的编译器在 this 特定情况下的奇怪行为的解释,因为 OP 专门询问了它。
  • 调整 C 源代码以优化 -O0 是愚蠢的,但是想知道函数的不同 asm 版本的性能差异可能很有趣,忽略它们都来自编译器输出的事实。我已经回答了几个类似的 x86 问题。所以这个问题措辞不好,重点是 C 差异而不是 asm 差异。
【解决方案3】:

一般来说:使用指针没有理由让程序运行得更快。在没有启用所有优化的情况下讨论程序的性能,就像课程创建者在你的报价中所做的那样,是没有意义的。这当然不是改变编写代码方式的理由。

另一个旧的、经常使用但已过时的技巧是将循环编写为向下计数而不是向上计数,因为与零比较通常比与值比较更快。但这也是您不应该让影响您编写代码的方式的事情,因为现代编译器可以为您进行优化。

程序员应该做的,以及编写课程的人应该教的,是尽可能简单易读地编写代码。这意味着您的两个示例都很糟糕,因为它们不必要地晦涩难懂,并且是“未成熟优化”的示例。更好的代码是:

  int counter;
  ...
  for(counter=0; counter < 6; counter++)
  {}

这与代码的可读性差不多,没有理由相信上述代码在任何已知系统上的性能都会比您的示例差。

这样做:

  • 尽可能编写可读性最强的代码。
  • 在发布中,启用优化。
  • 如果存在性能问题,请进行基准测试并找到瓶颈。
  • 根据需要手动优化瓶颈。可能考虑到特定的系统。

【讨论】:

    【解决方案4】:

    原来的更新提供了一个很好的答案。此外,原来的“计数器”是一个全局变量,因此在ARM芯片上的每次访问都需要先将变量加载到寄存器中。根据变量的位置和优化级别,这至少是一条 LDR 指令(可能更多),然后更新计数器++需要添加指令和写回全局变量。

    如果计数器被声明为局部变量,那么实际上使用指针版本会不太理想。在这种情况下,大多数编译器会将计数器分配到寄存器中,然后访问它会非常快。如果使用指针,计数器将被强制分配给堆栈(因为它的地址被分配给“p”),并且需要更多指令来进行加法和访问。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-24
      • 2012-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-29
      • 2012-10-07
      • 1970-01-01
      相关资源
      最近更新 更多