所以今天我正在尝试优化链表遍历。我的想法是,当我只能复制一份时,将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()函数太慢了吗?
答案 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:
现在,让我们试着描述每个中发生的事情:
在修剪中,执行以下步骤:
换句话说,每次迭代包含3个副本,1个测试和1个跳转指令。
现在,你的聪明优化的tumbletrim:
换句话说,在 final 迭代中,当您退出循环时,执行的指令总数为:
在所有其他迭代中,总计数如下所示:
所以在极少数情况下(退出循环之前的最后一次迭代),你的实现更便宜当且仅当跳转指令比从寄存器复制指针大小的值更便宜(它不是)
在常见情况下(所有其他迭代,您的实现有更多副本和更多比较。(和更多指令,在指令缓存上加载更多。并且更多分支语句,增加了更多负载)分支缓存)
现在,如果您首先关注的性能,那么您还有两个更基本的错误:
std::list
)