2010-06-16 48 views
7

Mã Tôi có trông như thế này (tất cả sử dụng của xong hiển thị):Trình biên dịch có tối ưu hóa thoát khỏi vòng lặp bên trong không?

bool done = false; 
for(int i = 0; i < big; i++) 
{ 
    ... 
    for(int j = 0; j < wow; j++) 
    { 
    ... 
    if(foo(i,j)) 
    { 
     done = true; 
     break; 
    } 
    ... 
    } 
    if(done) break; 
    ... 
} 

sẽ bất kỳ trình biên dịch chuyển nó sang này:

for(int i = 0; i < big; i++) 
{ 
    ... 
    for(int j = 0; j < wow; j++) 
    { 
    ... 
    if(foo(i,j)) 
     goto __done; // same as a labeled break if we had it 
    ... 
    } 
    ... 
} 
__done:; 

Lưu ý: Trong khi tôi chủ yếu quan tâm nếu số if(done)break; bị bỏ qua và bị xóa dưới dạng mã chết, tôi cũng quan tâm đến việc nó và done sẽ bị xóa hoàn toàn.

+3

Nhân tiện, bạn không nên xác định bất kỳ biểu tượng nào bắt đầu bằng hai dấu gạch dưới như thế; các ký hiệu như vậy được bảo lưu. –

+8

Biểu tượng sẽ là kết quả của một thẻ tối ưu hóa, e.i. được tạo bởi trình biên dịch. Tôi đã sử dụng tên đó * vì * nó chỉ ra một tên được đặt trước/bên trong. – BCS

+0

Và câu hỏi: tại sao nó không bị giấu trong một chức năng?Bạn có thể sử dụng 'return' rồi;) –

Trả lời

14

Rõ ràng điều này phụ thuộc vào trình biên dịch. Điều tốt nhất để làm khi bạn không chắc chắn là để xem đầu ra lắp ráp của trình biên dịch (tất cả các trình biên dịch phổ biến có một chuyển đổi cho điều này). Ngay cả khi bạn không quen với việc lắp ráp, bạn có thể ít nhất so sánh phiên bản gỡ lỗi với phiên bản được tối ưu hóa.

Điều đó đang được nói, đây là một trong số ít trường hợp mà goto KHÔNG phải là ý tưởng tồi. Hãy sử dụng nó để thoát ra khỏi vòng trong.

Sửa

Chỉ cần thử như sau trong VS2010 và nó thực sự tối ưu hóa bên ngoài có điều kiện:

bool done = false; 
for(int i = 0; i < 10; i++) 
{ 
    for(int j = 0; j < 10; j++) 
    { 
     if(i == 7 && j == 3) 
     { 
      done = true; 
      break; 
     } 
    } 
    if(done) break; 
} 
return 0; 
+16

+1 cho chủ nghĩa thực dụng. Quá nhiều người đã mất dấu của thực tế rằng gotos, phá vỡ, nhiều điểm trở lại từ chức năng, và những thứ khác, là xấu _only khi họ làm cho mã khó hiểu._ Sử dụng khôn ngoan những điều như vậy là tốt. – paxdiablo

+1

Được in đậm để đảm bảo không ai bỏ lỡ nó :) – Cogwheel

+0

Yah, một goto là hợp pháp ở đó. OTOH một sự phá vỡ có nhãn thậm chí còn tốt hơn và nếu nó tránh tôi phải bảo vệ nó trong một bài đánh giá mã, tôi sẽ sống với trình biên dịch làm một số phép thuật cho tôi. – BCS

7

GNU trình biên dịch không chỉ đó, bắt đầu với mức độ tối ưu hóa -O1 (Tôi đang sử dụng gcc 4.5.1 trên x86_64)

call _Z3fooii // al = foo(i,j) 
testb %al, %al 
jne .L14 
... 

nơi .L14 là nhãn được đặt chính xác nơi bạn đặt __done:

Câu hỏi hay hơn có thể là: trình biên dịch hiện đại nào không thực hiện tối ưu hóa này?

+1

SNC - ít nhất, không phải phiên bản chúng tôi có. – Crashworks

1

Tôi đã thử GCC 4.2.1 như sau:

// Prevent optimizing out calls to foo and loop unrolling: 
extern int big, wow; 
bool foo(int,int); 

void 
bar() 
{ 
    int done = false; 
    for(int i = 0; i < big; i++) 
    { 
     for(int j = 0; j < wow; j++) 
     { 
      if(foo(i,j)) 
      { 
       done = true; 
       break; 
      } 
     } 
     if(done) 
      break; 
    } 
} 

... và nó rơi qua thẳng đến postamble với -O3:

33: e8 fc ff ff ff   call 34 <bar()+0x34> ; call to foo* 
    38: 84 c0     test %al,%al 
    3a: 74 e5     je  21 <bar()+0x21> ; next loop iteration 
    3c: 83 c4 10    add $0x10,%esp 
    3f: 5b      pop %ebx 
    40: 5e      pop %esi 
    41: 5d      pop %ebp 
    42: c3      ret 

*** Đây là từ một tệp đối tượng chưa được liên kết, call 34 thực sự gọi tới foo.

+1

vì vậy các nhánh '3a' trực tiếp đến điểm cuối cùng ở '42'? – BCS

+0

@BCS, 0x3a nhánh vào đầu vòng lặp nếu AL bằng không (foo trả về false), nếu không nó chỉ tiếp tục postamble (pops lưu sổ đăng ký, khôi phục khung ngăn xếp trước) 3c, 3f, .. cho đến khi ret –

4

Tôi không cố gắng để được snarky, nhưng ... không quan trọng? Nói chung, tôi nghĩ tốt nhất là để các trình biên dịch cho công việc của họ, và công việc đó là tạo ra "tốt nhất" (lưu ý rằng "tốt nhất" có thể thay đổi tùy theo nhu cầu của bạn) được biên dịch mã cho mã nguồn của bạn. Bất kỳ sự cân nhắc hiệu suất nào trong mã của bạn phải được xác định với một kiến ​​thức làm việc sâu sắc và hiệu quả về độ phức tạp của thuật toán.

Nếu bạn chỉ tò mò, hãy bỏ qua nhận xét này. Nhưng nếu ý định của bạn là bằng cách nào đó tối ưu hóa mã của bạn, thì tôi nghĩ có nhiều con đường tốt hơn.

+1

Rất nhiều các trình biên dịch không thực hiện công việc đó rất tốt - ít nhất, cũng không như chúng ta cho là họ làm. – Crashworks

+0

@Crashworks, vấn đề với việc tối ưu hóa vi mô cho một trình biên dịch cụ thể chỉ là, đó là trình biên dịch cụ thể. Mã của bạn có thể khôi phục đáng kể các phiên bản tiếp theo của trình biên dịch đã nói nếu bạn chủ động ngăn chặn nó làm một số tối ưu hóa mới. Nó tốt hơn để tối ưu hóa nơi bạn biết trình biên dịch không thể tối ưu hóa do ràng buộc ngôn ngữ (sao chép đối tượng quá mức, răng cưa, vv). Trên tiếp tuyến đó, thật khó để biết tối ưu hóa nào không được thực hiện vì trình biên dịch thiếu, và đó là vì đặc tả ngôn ngữ không cho phép điều đó. –

+2

Tôi phần nào đồng ý, Crash, nhưng tôi vẫn nghĩ điều cơ bản quan trọng là hiểu được sự phức tạp về thuật toán. Trình biên dịch tốt nhất trên thế giới không thể sửa chữa một số tìm kiếm tuyến tính kém chất lượng hoặc mã nối chuỗi kém. Tôi thấy nó hiệu quả hơn khi nghĩ về các trình biên dịch như một hộp đen vì nó buộc chúng ta phải chịu trách nhiệm về những thứ chúng ta có quyền kiểm soát nhiều nhất: mã riêng của chúng ta. – kidjan

Các vấn đề liên quan