【问题标题】:Dereferencing multiple pointers repeatedly, less efficient?重复取消引用多个指针,效率较低?
【发布时间】:2015-07-07 06:25:54
【问题描述】:

而不是写:

string name = first->next->next->next->name;
int age = first->next->next->next->age;

写成:

node* billy_block = first->next->next->next;

string name = billy_block->name;
int age = billy_block->age;

效率更高?变量billy_block 是否有可能被编译器“优化掉”?

我不懂编译器,所以请用简单的术语。

【问题讨论】:

  • 你说的“billy_block优化了”是什么意思?
  • 一个好的优化器可能会为 billy_block 分配一个寄存器,如果这就是你的要求
  • @CoffeeandCode,我不懂编译器。最终代码中是否可能根本不存在“billy_block” - 在堆栈上?
  • @samofoz 您的第一个和第二个示例可能会被优化为相同的目标代码,如果这就是您对 billy_block 所做的一切的话

标签: c


【解决方案1】:

任何自尊的现代编译器都会优化原始代码,以消除在安全的情况下对同一指针链的重复取消引用。

但是,首先,代码的原始版本比使用中间变量的代码可读性差得多。当指针解引用链很长时,人眼不会立即看出这些链是相同的。带有中间变量的变体清楚地表明我们想从同一个对象中读取nameage。在第一个变体中,它甚至没有那么清楚。

其次,在编译器的别名情况不像您的情况那样明显的情况下,编译器可能不得不放弃优化。例如。通常情况下,当您执行类似

的操作时
some_ptr->next = first->next->next->next->next;
some_ptr->prev = first->next->next->next->prev;

编译器不能确定第一个赋值不会影响first->next->next->next 的值。 (考虑如果some_ptr 等于first 会发生什么。)这会迫使编译器安全地运行它并从一开始就重新评估first->next->next->next。在这种情况下,引入中间变量确实会优化代码。为此,当然,您自己必须确保这样做是正确的,即在程序中使用您自己对可能的别名的了解。

【讨论】:

    【解决方案2】:

    编译器所做的优化取决于您使用的编译器以及您传递给它的优化标志。你可以随时检查编译器输出的汇编代码,看看它做了什么。

    例如,随 Xcode 一起安装的编译器(Apple LLVM 版本 6.1.0 (clang-602.0.53)(基于 LLVM 3.6.0svn)),具有 node 的适当定义,不会删除即使使用最高优化设置也会重复访问链:

        movq    %rdi, %rbx
        movq    (%rbx), %rax
        movq    (%rax), %rax
        movq    (%rax), %rsi
        addq    $8, %rsi
        leaq    -40(%rbp), %r14
        movq    %r14, %rdi
        callq   __ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC1ERKS5_
        movq    (%rbx), %rax
        movq    (%rax), %rax
        movq    (%rax), %rax
        movl    32(%rax), %ebx
    

    如果您使用临时变量,尽管编译器只是重用了寄存器%rbx,那么您就可以免费获得临时变量:

        movq    (%rdi), %rax
        movq    (%rax), %rax
        movq    (%rax), %rbx
        leaq    8(%rbx), %rsi
        leaq    -40(%rbp), %r14
        movq    %r14, %rdi
        callq   __ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC1ERKS5_
        movl    32(%rbx), %ebx
    

    这是测试程序:

    #include <string>
    
    using namespace std;
    
    string myname;
    
    struct node {
        node *next;
        string name;
        int age;
    };
    
    int foo1(node *first) {
        string name = first->next->next->next->name;
        int age = first->next->next->next->age;
        myname=name;
        return age;
    }
    
    
    int foo2(node *first) {
        node* billy_block = first->next->next->next;
        string name = billy_block->name;
        int age = billy_block->age;
        myname=name;
        return age;
    }
    

    编译器选项g++ -Wall -S -O3 test.cpp

    【讨论】:

      【解决方案3】:

      编译器只能合并两个负载:

      int x = *p;
      ...;
      int y = *p;
      

      如果全部:

      • p 不是指向 volatile 的指针。
      • ... 期间没有可能出现别名的写入。

      别名分析的问题真的很困难,所以这主要意味着any写。另一方面,创建仅分配一次然后读取零次或多次的附加变量的问题基本上是编译器的工作方式,因此请不要犹豫添加临时变量。


      但是,如果有人遇到此答案并使用 C++,请注意析构函数排序。

      【讨论】:

        猜你喜欢
        • 2015-10-30
        • 1970-01-01
        • 1970-01-01
        • 2016-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-28
        • 2021-07-04
        相关资源
        最近更新 更多