繁体   English   中英

在C ++中优化指针副本

[英]Optimizing pointer copies in c++

因此,今天我正在尝试优化链表遍历。 我的想法是,当我只能复制一份时,复制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()函数这么慢?

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

这是两个函数生成的程序集的相关部分:(仅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. 如果满足条件,则跳到循环的开始

换句话说,每个迭代包含3个副本,1个测试和1个跳转指令。

现在,您巧妙地优化了tumbletrim:

  1. 复制2个指针大小的值
  2. 测试休息条件
  3. 如果满足条件,则跳到循环结束
  4. 否则复制指针大小的值
  5. 测试while循环的条件
  6. 复制指针大小的值
  7. 跳到循环的开始

换句话说,在最后的迭代中,当您退出循环时,执行的指令总数为:

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

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

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

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

在常见情况下(所有其他迭代,您的实现具有更多的副本更多的比较。(更多的指令,给指令高速缓存带来更多的负载。更多的分支语句,向分支缓存带来更多的负载)

现在,如果你在所有关心摆在首位的表现,那么有两个你做错了更为基本的东西:

  1. 您正在使用链接列表。 链接列表的执行速度很慢,这是因为它们执行的算法(这涉及到在内存中跳转,因为节点没有连续分配),而不是因为实现。 因此,无论您的实现多么聪明,它都无法弥补底层算法的糟糕性
  2. 您正在编写自己的链接列表。 如果绝对必须使用链表,请使用专家撰写的std::liststd::list

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM