簡體   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