在c ++中优化指针副本

时间:2014-05-13 15:12:04

标签: c++ pointers optimization

所以今天我正在尝试优化链表遍历。我的想法是,当我只能复制一份时,将cur复制到最后,然后在cur旁边的效率会降低。希望下面的代码有助于使其更清晰:

struct Node{
    int body;
    Node* next;
};

Node* construct(int len){
    Node *head, *ptr, *end;
    head = new Node();
    ptr = head;
    ptr->body = 0;
    for(int i=1; i<len; i++){
        end = new Node();
        end->next = NULL;
        end->body = i;

        ptr->next = end;
        ptr = end;
    }
    return head;
}

int len(Node* ptr){
    int i=1;
    while(ptr->next){
        ptr = ptr->next;
        i += 1;
    }
    return i;
}

void trim(Node* head){
    Node *last, *cur;
    cur = head;
    while(cur->next){
        last = cur;
        cur = cur->next;
    }
    last->next = NULL;
}

void tumble_trim(Node* head){ // This one uses less copies per traverse
    Node *a, *b;
    a = head;
    while(true){
        if(!a->next){
            b->next = NULL;
            break;
        }
        b = a->next;
        if(!b->next){
            a->next = NULL;
            break;
        }
        a = b->next;
    }
}

int main(){
    int start;
    Node *head;

    start = clock();
    head = construct(100000);
    for(int i=0; i<5000; i++){
        trim(head);
    }
    cout << clock()-start << endl;

    start = clock();
    head = construct(100000);
    for(int i=0; i<5000; i++){
        tumble_trim(head);
    }
    cout << clock()-start << endl;
}

然而,结果对我来说非常令人惊讶。实际上副本较少的那个较慢:

1950000
2310000 // I expected this one to be faster

有人可以解释为什么tumble_trim()函数太慢了吗?

2 个答案:

答案 0 :(得分:3)

您的编译器显然比trim()更优化tumble_trim()。这是保证代码简单易读的主要示例,并且在您通过性能分析确定瓶颈后,尝试任何优化。即使这样,你也很难在像这样的简单循环上击败编译器。

答案 1 :(得分:1)

这里是两个函数生成的程序集的相关部分:(只是while循环:

修剪:

LBB2_1:                                 ## =>This Inner Loop Header: Depth=1
    movq    %rcx, %rax
    movq    %rdi, %rcx
    movq    8(%rdi), %rdi
    testq   %rdi, %rdi
    jne LBB2_1
## BB#2:

tumbletrim:

LBB3_1:                                 ## =>This Inner Loop Header: Depth=1
    movq    %rdi, %rax
    movq    8(%rax), %rdx
    testq   %rdx, %rdx
    je  LBB3_2
## BB#3:                                ##   in Loop: Header=BB3_1 Depth=1
    movq    8(%rdx), %rdi
    testq   %rdi, %rdi
    movq    %rdx, %rcx
    jne LBB3_1
## BB#4:
    movq    $0, 8(%rax)
    popq    %rbp
    ret
LBB3_2:

现在,让我们试着描述每个中发生的事情:

在修剪中,执行以下步骤:

  1. 复制3个指针大小的值
  2. 测试while循环的条件
  3. 如果满足条件,则跳转到循环的开头
  4. 换句话说,每次迭代包含3个副本,1个测试和1个跳转指令。

    现在,你的聪明优化的tumbletrim:

    1. 复制2个指针大小的值
    2. 测试休息的条件
    3. 如果满足条件,则跳转到循环结束
    4. else复制指针大小的值
    5. 测试while循环的条件
    6. 复制指针大小的值
    7. 跳转到循环的开头
    8. 换句话说,在 final 迭代中,当您退出循环时,执行的指令总数为:

      • 修剪:3个指针副本,1个比较
      • tumbletrim:2指针,1比较,1跳

      在所有其他迭代中,总计数如下所示:

      • 修剪:3个指针副本,1个比较,1个跳转
      • tumbletrim:4个指针副本,2个比较,1个跳转

      所以在极少数情况下(退出循环之前的最后一次迭代),你的实现更便宜当且仅当跳转指令比从寄存器复制指针大小的值更便宜(它不是)

      在常见情况下(所有其他迭代,您的实现有更多副本更多比较。(和更多指令,在指令缓存上加载更多。并且更多分支语句,增加了更多负载)分支缓存)

      现在,如果您首先关注的性能,那么您还有两个更基本的错误:

      1. 您正在使用链接列表。由于链接列表执行的算法(包括在内存中跳转,因为节点没有连续分配),并且不是因为实现,因此链接列表很慢。所以无论你的实现多么聪明,它都不能弥补潜在的算法很糟糕
      2. 您正在编写自己的链接列表。如果您绝对必须使用链接列表,请使用专家编写的链接列表(std::list