Tối ưu hóa một cách nhanh chóng trong khi (1); trong C ++ 0x


153

Cập nhật, xem bên dưới!

Tôi đã nghe và đọc rằng C ++ 0x cho phép trình biên dịch in "Xin chào" cho đoạn trích sau

#include <iostream>

int main() {
  while(1) 
    ;
  std::cout << "Hello" << std::endl;
}

Nó rõ ràng có một cái gì đó để làm với chủ đề và khả năng tối ưu hóa. Dường như với tôi rằng điều này có thể làm nhiều người ngạc nhiên.

Có ai có một lời giải thích tốt về lý do tại sao điều này là cần thiết để cho phép? Để tham khảo, bản nháp C ++ 0x gần đây nhất cho biết tại6.5/5

Một vòng lặp, bên ngoài câu lệnh for-init trong trường hợp câu lệnh for,

  • không thực hiện cuộc gọi đến các chức năng I / O của thư viện và
  • không truy cập hoặc sửa đổi các đối tượng dễ bay hơi và
  • thực hiện không có hoạt động đồng bộ hóa (1.10) hoặc hoạt động nguyên tử (Điều 29)

có thể được giả định bởi việc thực hiện để chấm dứt. [Lưu ý: Điều này nhằm cho phép chuyển đổi trình biên dịch, chẳng hạn như loại bỏ các vòng lặp trống, ngay cả khi việc chấm dứt không thể được chứng minh. - lưu ý cuối]

Biên tập:

Bài viết sâu sắc này nói về văn bản Tiêu chuẩn đó

Thật không may, các từ "hành vi không xác định" không được sử dụng. Tuy nhiên, bất cứ lúc nào tiêu chuẩn nói "trình biên dịch có thể giả sử P", điều đó ngụ ý rằng một chương trình có thuộc tính không-P có ngữ nghĩa không xác định.

Điều đó có đúng không, và trình biên dịch có được phép in "Tạm biệt" cho chương trình trên không?


Có một chủ đề thậm chí sâu sắc hơn ở đây , đó là về một sự thay đổi tương tự với C, được bắt đầu bởi Guy đã thực hiện bài viết được liên kết ở trên. Trong số các sự kiện hữu ích khác, họ đưa ra một giải pháp dường như cũng áp dụng cho C ++ 0x ( Cập nhật : Điều này sẽ không hoạt động nữa với n3225 - xem bên dưới!)

endless:
  goto endless;

Một trình biên dịch không được phép tối ưu hóa điều đó, dường như, bởi vì đó không phải là một vòng lặp, mà là một bước nhảy. Một anh chàng khác tóm tắt sự thay đổi được đề xuất trong C ++ 0x và C201X

Bằng cách viết một vòng lặp, các lập trình viên đang khẳng định một trong hai mà vòng lặp làm điều gì đó với hành vi có thể nhìn thấy (Thực hiện I / O, truy cập đối tượng không ổn định, hoặc thực hiện đồng bộ hoặc các hoạt động nguyên tử), hoặc rằng nó cuối cùng chấm dứt. Nếu tôi vi phạm giả định đó bằng cách viết một vòng lặp vô hạn không có tác dụng phụ, tôi đang nói dối với trình biên dịch và hành vi của chương trình của tôi không được xác định. (Nếu tôi may mắn, trình biên dịch có thể cảnh báo tôi về điều đó.) Ngôn ngữ không cung cấp (không còn cung cấp?) Một cách để thể hiện một vòng lặp vô hạn mà không có hành vi có thể nhìn thấy.


Cập nhật ngày 3.1.2011 với n3225: Ủy ban đã chuyển văn bản thành 1.10 / 24 và nói

Việc thực hiện có thể cho rằng bất kỳ luồng nào cuối cùng cũng sẽ thực hiện một trong các thao tác sau:

  • chấm dứt,
  • thực hiện cuộc gọi đến chức năng I / O của thư viện
  • truy cập hoặc sửa đổi một đối tượng dễ bay hơi, hoặc
  • thực hiện một hoạt động đồng bộ hóa hoặc một hoạt động nguyên tử.

Thủ gotothuật sẽ không hoạt động nữa!


4
while(1) { MyMysteriousFunction(); }phải được biên dịch độc lập mà không biết định nghĩa của hàm bí ẩn đó, phải không? Vậy làm thế nào chúng ta có thể xác định nếu nó thực hiện cuộc gọi đến bất kỳ chức năng I / O thư viện nào? Nói cách khác: chắc chắn rằng viên đạn đầu tiên có thể được thực hiện khiến không có cuộc gọi nào đến chức năng .
Daniel Earwicker

19
@Daniel: Nếu nó có quyền truy cập vào định nghĩa của hàm, nó có thể chứng minh rất nhiều thứ. Có một thứ như tối ưu hóa liên vùng.
Potatoswatter

3
Ngay bây giờ, trong C ++ 03, trình biên dịch có được phép thay đổi int x = 1; for(int i = 0; i < 10; ++i) do_something(&i); x++;thành for(int i = 0; i < 10; ++i) do_something(&i); int x = 2;? Hoặc có thể theo cách khác, với xviệc được khởi tạo 2trước vòng lặp. Nó có thể cho biết do_somethingkhông quan tâm đến giá trị của nó x, vì vậy nó là một tối ưu hóa hoàn toàn an toàn, nếu do_something không khiến giá trị ithay đổi sao cho bạn kết thúc trong một vòng lặp vô hạn.
Dennis Zickefoose

4
Vì vậy, điều này có nghĩa là main() { start_daemon_thread(); while(1) { sleep(1000); } }có thể thoát ngay lập tức thay vì chạy daemon của tôi trong một luồng nền?
Gabe

2
"Bài viết sâu sắc này" giả định rằng một hành vi cụ thể là Hành vi không xác định chỉ vì không có hành vi rõ ràng, được xác định. Đó là một giả định không chính xác. Nói chung, khi tiêu chuẩn để lại một số hành vi hữu hạn, việc cấy ghép phải chọn bất kỳ hành vi nào ( Hành vi không xác định ). Điều này không cần phải xác định. Cho dù một vòng lặp không làm gì chấm dứt được cho là một lựa chọn boolean; hoặc nó không hoặc nó không. Làm những việc khác không được phép.
MSalters

Câu trả lời:


33

Có ai có một lời giải thích tốt về lý do tại sao điều này là cần thiết để cho phép?

Có, Hans Boehm cung cấp một lý do cho điều này trong N1528: Tại sao hành vi không xác định cho các vòng lặp vô hạn? , mặc dù đây là tài liệu WG14, lý do cũng áp dụng cho C ++ và tài liệu đề cập đến cả WG14 và WG21:

Như N1509 chỉ ra một cách chính xác, bản dự thảo hiện tại về cơ bản đưa ra hành vi không xác định cho các vòng lặp vô hạn trong 6.8.5p6. Một vấn đề lớn để làm như vậy là nó cho phép mã di chuyển qua một vòng lặp có khả năng không kết thúc. Ví dụ: giả sử chúng ta có các vòng lặp sau, trong đó Count và Count2 là các biến toàn cục (hoặc đã lấy địa chỉ của chúng) và p là biến cục bộ, địa chỉ chưa được lấy:

for (p = q; p != 0; p = p -> next) {
    ++count;
}
for (p = q; p != 0; p = p -> next) {
    ++count2;
}

Hai vòng lặp này có thể được hợp nhất và thay thế bằng vòng lặp sau không?

for (p = q; p != 0; p = p -> next) {
        ++count;
        ++count2;
}

Nếu không có sự phân phối đặc biệt trong 6.8.5p6 cho các vòng lặp vô hạn, điều này sẽ không được phép: Nếu vòng lặp đầu tiên không kết thúc vì q trỏ vào danh sách vòng tròn, bản gốc không bao giờ ghi vào đếm2. Do đó, nó có thể được chạy song song với một luồng khác truy cập hoặc cập nhật Count2. Điều này không còn an toàn với phiên bản đã chuyển đổi, truy cập Count2 bất chấp vòng lặp vô hạn. Do đó, việc chuyển đổi có khả năng giới thiệu một cuộc đua dữ liệu.

Trong những trường hợp như thế này, rất khó có khả năng trình biên dịch có thể chứng minh chấm dứt vòng lặp; nó sẽ phải hiểu rằng q trỏ đến một danh sách theo chu kỳ, mà tôi tin là vượt quá khả năng của hầu hết các trình biên dịch chính, và thường là không thể nếu không có thông tin toàn bộ chương trình.

Các hạn chế được áp đặt bởi các vòng lặp không kết thúc là một hạn chế về tối ưu hóa các vòng lặp kết thúc mà trình biên dịch không thể chứng minh được sự chấm dứt, cũng như tối ưu hóa các vòng lặp thực sự không kết thúc. Cái trước là phổ biến hơn nhiều so với cái sau, và thường thú vị hơn để tối ưu hóa.

Rõ ràng cũng có các vòng lặp for với một biến vòng lặp số nguyên, trong đó trình biên dịch sẽ khó chứng minh sự kết thúc và do đó trình biên dịch sẽ khó tái cấu trúc các vòng lặp nếu không có 6.8.5p6. Thậm chí một cái gì đó như

for (i = 1; i != 15; i += 2)

hoặc là

for (i = 1; i <= 10; i += j)

dường như không cần thiết để xử lý. (Trong trường hợp trước, một số lý thuyết số cơ bản được yêu cầu để chứng minh sự chấm dứt, trong trường hợp sau, chúng ta cần biết điều gì đó về các giá trị có thể có của j để làm như vậy. )

Vấn đề này dường như áp dụng cho hầu hết tất cả các phép biến đổi cấu trúc vòng lặp, bao gồm các phép biến đổi song song hóa trình biên dịch và tối ưu hóa bộ đệm, cả hai đều có khả năng đạt được tầm quan trọng và thường rất quan trọng đối với mã số. Điều này dường như biến thành một chi phí đáng kể vì lợi ích của việc có thể viết các vòng lặp vô hạn theo cách tự nhiên nhất có thể, đặc biệt vì hầu hết chúng ta hiếm khi viết các vòng lặp vô hạn có chủ ý.

Một điểm khác biệt lớn với C là C11 cung cấp một ngoại lệ để kiểm soát các biểu thức là các biểu thức không đổi khác với C ++ và làm cho ví dụ cụ thể của bạn được xác định rõ trong C11.


1
Có bất kỳ tối ưu hóa an toàn và hữu ích nào được tạo điều kiện bởi ngôn ngữ hiện tại cũng sẽ không được tạo điều kiện bằng cách nói "Nếu việc chấm dứt vòng lặp phụ thuộc vào trạng thái của bất kỳ đối tượng nào, thời gian cần thiết để thực hiện vòng lặp không được coi là một tác dụng phụ có thể quan sát được, ngay cả khi thời gian đó xảy ra là vô hạn ". Đưa ra do { x = slowFunctionWithNoSideEffects(x);} while(x != 23);mã Hoist sau vòng lặp không phụ thuộc vào xcó vẻ an toàn và hợp lý, nhưng cho phép trình biên dịch giả định x==23mã như vậy có vẻ nguy hiểm hơn là hữu ích.
supercat

47

Đối với tôi, sự biện minh có liên quan là:

Điều này nhằm mục đích cho phép chuyển đổi trình biên dịch, chẳng hạn như loại bỏ các vòng lặp trống, ngay cả khi việc chấm dứt không thể được chứng minh.

Có lẽ, điều này là do việc chứng minh chấm dứt một cách cơ học là khó khăn và việc không thể chứng minh việc chấm dứt cản trở các trình biên dịch có thể thực hiện các phép biến đổi hữu ích, chẳng hạn như chuyển các hoạt động không phụ thuộc từ trước vòng lặp sang sau hoặc ngược lại, thực hiện các hoạt động sau vòng lặp trong một luồng trong khi vòng lặp thực thi trong cái khác, v.v. Nếu không có các phép biến đổi này, một vòng lặp có thể chặn tất cả các luồng khác trong khi chúng chờ đợi một luồng kết thúc vòng lặp đã nói. (Tôi sử dụng "luồng" một cách lỏng lẻo để có nghĩa là bất kỳ hình thức xử lý song song nào, bao gồm các luồng lệnh VLIW riêng biệt.)

EDIT: Ví dụ ngu ngốc:

while (complicated_condition()) {
    x = complicated_but_externally_invisible_operation(x);
}
complex_io_operation();
cout << "Results:" << endl;
cout << x << endl;

Ở đây, sẽ nhanh hơn khi một luồng thực hiện complex_io_operationtrong khi luồng kia đang thực hiện tất cả các phép tính phức tạp trong vòng lặp. Nhưng không có mệnh đề bạn đã trích dẫn, trình biên dịch phải chứng minh hai điều trước khi có thể tối ưu hóa: 1) complex_io_operation()không phụ thuộc vào kết quả của vòng lặp và 2) vòng lặp sẽ chấm dứt . Chứng minh 1) là khá dễ dàng, chứng minh 2) là vấn đề tạm dừng. Với mệnh đề, nó có thể giả sử vòng lặp chấm dứt và có được chiến thắng song song.

Tôi cũng tưởng tượng rằng các nhà thiết kế đã xem xét rằng các trường hợp xảy ra các vòng lặp vô hạn trong mã sản xuất là rất hiếm và thường là những thứ như các vòng lặp theo hướng sự kiện truy cập I / O theo một cách nào đó. Kết quả là, họ đã bi quan hóa trường hợp hiếm gặp (vòng lặp vô hạn) để tối ưu hóa trường hợp phổ biến hơn (không vô hạn, nhưng khó chứng minh một cách cơ học các vòng lặp không vô hạn).

Tuy nhiên, điều đó có nghĩa là các vòng lặp vô hạn được sử dụng trong các ví dụ học tập sẽ bị ảnh hưởng và sẽ nâng cao các vấn đề về mã người mới bắt đầu. Tôi không thể nói đây hoàn toàn là một điều tốt.

EDIT: liên quan đến bài viết sâu sắc mà bạn hiện liên kết, tôi sẽ nói rằng "trình biên dịch có thể giả sử X về chương trình" tương đương về mặt logic với "nếu chương trình không thỏa mãn X, thì hành vi không được xác định". Chúng ta có thể chỉ ra điều này như sau: giả sử tồn tại một chương trình không thỏa mãn thuộc tính X. Hành vi của chương trình này sẽ được xác định ở đâu? Tiêu chuẩn chỉ định nghĩa hành vi giả sử thuộc tính X là đúng. Mặc dù Tiêu chuẩn không tuyên bố rõ ràng hành vi không xác định, nhưng nó đã tuyên bố nó không được xác định bởi thiếu sót.

Xem xét một đối số tương tự: "trình biên dịch có thể giả sử một biến x chỉ được gán cho nhiều nhất một lần giữa các điểm chuỗi" tương đương với "gán cho x nhiều hơn một lần giữa các điểm chuỗi không được xác định".


"Chứng minh 1) là khá dễ dàng" - trên thực tế, nó không tuân theo ngay 3 điều kiện để trình biên dịch được phép giả sử chấm dứt vòng lặp theo mệnh đề mà Julian đang hỏi về? Tôi nghĩ rằng họ có nghĩa là "vòng lặp không có tác dụng có thể quan sát được, ngoại trừ có thể quay mãi mãi" và điều khoản đảm bảo rằng "quay mãi mãi" không được đảm bảo hành vi cho các vòng lặp như vậy.
Steve Jessop

@Steve: thật dễ dàng nếu vòng lặp không kết thúc; nhưng nếu vòng lặp chấm dứt thì nó có thể có hành vi không cần thiết ảnh hưởng đến việc xử lý complex_io_operation.
Philip Potter

Rất tiếc, vâng, tôi đã bỏ lỡ rằng nó có thể sửa đổi các địa phương / bí danh không biến động / bất cứ thứ gì được sử dụng trong IO op. Vì vậy, bạn đã đúng: mặc dù không nhất thiết phải tuân theo, có nhiều trường hợp trình biên dịch có thể và chứng minh rằng không có sửa đổi như vậy xảy ra.
Steve Jessop

"Tuy nhiên, điều đó có nghĩa là các vòng lặp vô hạn được sử dụng trong các ví dụ học tập sẽ bị ảnh hưởng và sẽ tăng gotchas trong mã người mới bắt đầu. Tôi không thể nói đây hoàn toàn là một điều tốt." Chỉ cần biên dịch với tối ưu hóa và nó vẫn hoạt động
KitsuneYMG

1
@supercat: Những gì bạn mô tả là những gì sẽ xảy ra trong thực tế, nhưng đó không phải là những gì tiêu chuẩn dự thảo yêu cầu. Chúng ta không thể giả sử trình biên dịch không biết liệu một vòng lặp sẽ chấm dứt. Nếu trình biên dịch không biết vòng lặp sẽ không chấm dứt, nó có thể làm bất cứ điều gì nó thích. Các DS9K sẽ tạo quỷ mũi cho bất kỳ vòng lặp vô hạn không có I / O vv (Do đó, phá được DS9K vấn đề ngăn chặn.)
Philip Potter

15

Tôi nghĩ cách giải thích chính xác là từ bản chỉnh sửa của bạn: các vòng lặp vô hạn trống là hành vi không xác định.

Tôi sẽ không nói đó là hành vi đặc biệt trực quan, nhưng cách giải thích này có ý nghĩa hơn so với cách thay thế, rằng trình biên dịch được phép tùy ý bỏ qua các vòng lặp vô hạn mà không cần gọi UB.

Nếu vòng lặp vô hạn là UB, nó chỉ có nghĩa rằng các chương trình không chấm dứt không được coi là có ý nghĩa: theo C ++ 0x, họ không có ngữ nghĩa.

Điều đó cũng có ý nghĩa nhất định. Chúng là một trường hợp đặc biệt, trong đó một số tác dụng phụ không còn xảy ra nữa (ví dụ, không có gì được trả lại main) và một số tối ưu hóa trình biên dịch bị cản trở do phải bảo toàn các vòng lặp vô hạn. Ví dụ, việc tính toán di chuyển trên vòng lặp là hoàn toàn hợp lệ nếu vòng lặp không có tác dụng phụ, vì cuối cùng, việc tính toán sẽ được thực hiện trong mọi trường hợp. Nhưng nếu vòng lặp không bao giờ kết thúc, chúng ta không thể sắp xếp lại mã một cách an toàn trên đó, bởi vì chúng ta thể thay đổi hoạt động nào thực sự được thực hiện trước khi chương trình bị treo. Trừ khi chúng ta coi một chương trình treo là UB, nghĩa là như vậy.


7
Vòng lặp vô hạn trống rỗng là hành vi không xác định Alan Turing sẽ cầu xin khác biệt, nhưng chỉ khi anh ta quay cuồng trong mộ.
Donal Fellows

11
@Donal: Tôi chưa bao giờ nói bất cứ điều gì về ngữ nghĩa của nó trong máy Turing. Chúng ta đang thảo luận về ngữ nghĩa của một vòng lặp vô hạn không có hiệu ứng phụ trong C ++ . Và khi tôi đọc nó, C ++ 0x chọn nói rằng các vòng lặp như vậy không được xác định.
jalf

Các vòng lặp vô hạn trống sẽ là ngớ ngẩn, và không có lý do gì để có các quy tắc đặc biệt cho chúng. Quy tắc này được thiết kế để đối phó với các vòng lặp hữu ích trong thời gian không bị ràng buộc (hy vọng không phải là vô hạn), tính toán một cái gì đó sẽ cần thiết trong tương lai nhưng không phải ngay lập tức.
supercat

1
Điều này có nghĩa là C ++ 0x không phù hợp với các thiết bị nhúng? Hầu như tất cả các thiết bị nhúng là không kết thúc và thực hiện công việc của họ trong một chất béo lớn while(1){...}. Họ thậm chí thường xuyên sử dụng while(1);để gọi một thiết lập lại có sự trợ giúp của cơ quan giám sát.
vsz

1
@vsz: hình thức đầu tiên là tốt. Các vòng lặp vô hạn được xác định hoàn hảo, miễn là chúng có một số loại hành vi có thể quan sát được. Hình thức thứ hai phức tạp hơn, nhưng tôi có thể nghĩ ra hai cách rất dễ: (1) trình biên dịch nhắm mục tiêu các thiết bị nhúng chỉ có thể chọn xác định hành vi chặt chẽ hơn trong trường hợp đó hoặc (2) bạn tạo một cơ thể gọi một số chức năng thư viện giả . Miễn là trình biên dịch không biết hàm đó làm gì, nó phải giả sử rằng nó có thể có một số tác dụng phụ, và vì vậy nó không thể gây rối với vòng lặp.
jalf

8

Tôi nghĩ rằng đây là dọc theo dòng của loại câu hỏi này , tham chiếu một chủ đề khác . Tối ưu hóa đôi khi có thể loại bỏ các vòng lặp trống.


3
Câu hỏi hay. Có vẻ như anh chàng đó đã có chính xác vấn đề mà đoạn này cho phép trình biên dịch đó gây ra. Trong cuộc thảo luận được liên kết bởi một trong những câu trả lời, người ta viết rằng "Thật không may, các từ 'hành vi không xác định' không được sử dụng. Tuy nhiên, bất cứ lúc nào tiêu chuẩn nói 'trình biên dịch có thể giả sử P', nó ngụ ý rằng một chương trình có thuộc tính không-P có ngữ nghĩa không xác định. " . Điều này làm tôi ngạc nhiên. Điều này có nghĩa là chương trình ví dụ của tôi ở trên có hành vi không xác định và có thể tách ra khỏi hư không?
Julian Schaub - litb

@Johannes: văn bản "có thể được giả định" không xuất hiện ở bất kỳ nơi nào khác trong dự thảo mà tôi phải trao và "có thể giả định" chỉ xảy ra một vài lần. Mặc dù tôi đã kiểm tra điều này với chức năng tìm kiếm không khớp với các ngắt dòng, vì vậy tôi có thể đã bỏ lỡ một số. Vì vậy, tôi không chắc rằng sự khái quát của tác giả được bảo đảm dựa trên bằng chứng nhưng với tư cách là một nhà toán học, tôi phải thừa nhận logic của lập luận, rằng nếu trình biên dịch giả định điều gì đó sai thì nói chung nó có thể suy ra bất cứ điều gì ...
Steve Jessop

... Cho phép mâu thuẫn trong lý luận của người biên soạn về chương trình chắc chắn gợi ý rất mạnh về UB, vì đặc biệt nó cho phép trình biên dịch, đối với bất kỳ X nào, suy ra rằng chương trình tương đương với X. Chắc chắn cho phép trình biên dịch suy ra rằng cho phép nó để làm điều đó. Tôi cũng đồng ý với tác giả rằng nếu dự định UB thì nó phải được tuyên bố rõ ràng và nếu nó không có ý định thì văn bản đặc tả bị sai và nên được sửa (có lẽ bằng ngôn ngữ đặc tả tương đương, "trình biên dịch có thể thay thế vòng lặp với mã không có hiệu lực ", tôi không chắc chắn).
Steve Jessop

@SteveJessop: Bạn nghĩ gì khi nói rằng việc thực thi bất kỳ đoạn mã nào - bao gồm các vòng lặp vô hạn - có thể bị hoãn lại cho đến khi một đoạn mã nào đó ảnh hưởng đến hành vi của chương trình có thể quan sát được và vì mục đích đó quy tắc, thời gian cần thiết để thực thi một đoạn mã - ngay cả khi vô hạn - không phải là "hiệu ứng phụ có thể quan sát được". Nếu trình biên dịch có thể chứng minh rằng một vòng lặp không thể thoát mà không có biến giữ một giá trị nhất định, thì biến đó có thể được coi là giữ giá trị đó, thậm chí nó cũng có thể được hiển thị rằng vòng lặp không thể thoát với giá trị đó.
supercat

@supercat: như bạn đã nói kết luận đó, tôi không nghĩ nó sẽ cải thiện mọi thứ. Nếu vòng lặp có thể không bao giờ thoát thì đối với bất kỳ đối tượng Xvà mẫu bit nào x, trình biên dịch có thể chứng minh rằng vòng lặp không thoát mà không Xgiữ mẫu bit x. Đó là sự thật trống rỗng. Vì vậy, Xcó thể được coi là nắm giữ bất kỳ mẫu bit nào, và điều đó cũng tệ như UB theo nghĩa là sai Xxnó sẽ nhanh chóng gây ra một số. Vì vậy, tôi tin rằng bạn cần phải chính xác hơn trong tiêu chuẩn của bạn. Thật khó để nói về những gì xảy ra "ở cuối" một vòng lặp vô hạn và cho thấy nó tương đương với một số hoạt động hữu hạn.
Steve Jessop

8

Vấn đề liên quan là trình biên dịch được phép sắp xếp lại mã có tác dụng phụ không xung đột. Thứ tự thực hiện đáng ngạc nhiên có thể xảy ra ngay cả khi trình biên dịch tạo mã máy không kết thúc cho vòng lặp vô hạn.

Tôi tin rằng đây là cách tiếp cận đúng đắn. Thông số ngôn ngữ xác định các cách để thực thi thứ tự thực hiện. Nếu bạn muốn một vòng lặp vô hạn không thể sắp xếp lại xung quanh, hãy viết điều này:

volatile int dummy_side_effect;

while (1) {
    dummy_side_effect = 0;
}

printf("Never prints.\n");

2
@ JohannesSchaub-litb: Nếu một vòng lặp - vô tận hay không - không đọc hoặc ghi bất kỳ biến dễ bay hơi nào trong khi thực hiện và không gọi bất kỳ hàm nào có thể làm như vậy, trình biên dịch có thể tự do trì hoãn bất kỳ phần nào của vòng lặp cho đến khi nỗ lực đầu tiên để truy cập vào một cái gì đó được tính toán trong đó. Được đưa ra unsigned int dummy; while(1){dummy++;} fprintf(stderror,"Hey\r\n"); fprintf(stderror,"Result was %u\r\n",dummy);, cái đầu tiên fprintfcó thể thực thi, nhưng cái thứ hai thì không thể (trình biên dịch có thể di chuyển tính toán dummygiữa hai cái fprintf, nhưng không vượt qua cái được in giá trị của nó).
supercat

1

Tôi nghĩ vấn đề có lẽ tốt nhất có thể được nêu ra, vì "Nếu một đoạn mã sau không phụ thuộc vào một đoạn mã trước đó và đoạn mã trước đó không có tác dụng phụ đối với bất kỳ phần nào khác của hệ thống, thì đầu ra của trình biên dịch có thể thực thi đoạn mã sau trước, sau hoặc xen kẽ với việc thực thi mã trước, ngay cả khi mã trước chứa các vòng lặp, bất kể khi nào hoặc liệu mã cũ có thực sự hoàn thành hay không . Ví dụ, trình biên dịch có thể viết lại:

void testfermat (int n)
{
  int a = 1, b = 1, c = 1;
  while (pow (a, n) + pow (b, n)! = pow (c, n))
  {
    nếu (b> a) a ++; khác nếu (c> b) {a = 1; b ++}; khác {a = 1; b = 1; c ++};
  }
  printf ("Kết quả là");
  printf ("% d /% d /% d", a, b, c);
}

như

void testfermat (int n)
{
  if (fork_is_first_thread ())
  {
    int a = 1, b = 1, c = 1;
    while (pow (a, n) + pow (b, n)! = pow (c, n))
    {
      nếu (b> a) a ++; khác nếu (c> b) {a = 1; b ++}; khác {a = 1; b = 1; c ++};
    }
    signal_other_thread_and_die ();
  }
  khác // Chủ đề thứ hai
  {
    printf ("Kết quả là");
    Wait_for_other_thread ();
  }
  printf ("% d /% d /% d", a, b, c);
}

Nói chung không phải là không hợp lý, mặc dù tôi có thể lo lắng rằng:

  int tổng = 0;
  cho (i = 0; num numps> i; i ++)
  {
    update_proTHER_bar (i);
    tổng + = do_s Something_slow_with_no_side_effects (i);
  }
  show_result (tổng cộng);

sẽ trở thành

  int tổng = 0;
  if (fork_is_first_thread ())
  {
    cho (i = 0; num numps> i; i ++)
      tổng + = do_s Something_slow_with_no_side_effects (i);
    signal_other_thread_and_die ();
  }
  khác
  {
    cho (i = 0; num numps> i; i ++)
      update_proTHER_bar (i);
    Wait_for_other_thread ();
  }
  show_result (tổng cộng);

Bằng cách có một CPU xử lý các tính toán và một CPU khác xử lý các cập nhật thanh tiến trình, việc ghi lại sẽ cải thiện hiệu quả. Thật không may, nó sẽ làm cho các bản cập nhật thanh tiến trình ít hữu ích hơn mức cần thiết.


Tôi nghĩ rằng trường hợp thanh tiến trình của bạn không thể tách rời, vì hiển thị thanh tiến trình là một cuộc gọi I / O thư viện. Tối ưu hóa không nên thay đổi hành vi có thể nhìn thấy theo cách này.
Philip Potter

@Philip Potter: Nếu thói quen chậm có tác dụng phụ, điều đó chắc chắn là đúng. Trong ví dụ của tôi trước đây, sẽ là vô nghĩa nếu không, vì vậy tôi đã thay đổi nó. Giải thích của tôi về thông số kỹ thuật là hệ thống được phép trì hoãn việc thực thi mã chậm cho đến khi các hiệu ứng của nó (trừ thời gian cần thực thi) sẽ hiển thị, tức là lệnh show_result (). Nếu mã vạch tiến trình sử dụng tổng số đang chạy hoặc ít nhất là giả vờ làm như vậy, điều đó sẽ buộc nó phải đồng bộ hóa với mã chậm.
supercat

1
Điều này giải thích tất cả các thanh tiến trình đi nhanh từ 0 đến 100 và sau đó treo trong nhiều năm;)
paulm

0

Nó không thể quyết định cho trình biên dịch cho các trường hợp không tầm thường nếu nó là một vòng lặp vô hạn.

Trong các trường hợp khác nhau, có thể xảy ra rằng trình tối ưu hóa của bạn sẽ đạt được một lớp phức tạp tốt hơn cho mã của bạn (ví dụ: đó là O (n ^ 2) và bạn nhận được O (n) hoặc O (1) sau khi tối ưu hóa).

Vì vậy, để bao gồm một quy tắc như vậy không cho phép loại bỏ một vòng lặp vô hạn vào tiêu chuẩn C ++ sẽ làm cho nhiều sự tối ưu hóa là không thể. Và hầu hết mọi người không muốn điều này. Tôi nghĩ rằng điều này khá trả lời câu hỏi của bạn.


Một điều nữa: Tôi chưa bao giờ thấy bất kỳ ví dụ hợp lệ nào khi bạn cần một vòng lặp vô hạn mà không làm gì cả.

Một ví dụ tôi đã nghe nói về một vụ hack xấu xí thực sự cần phải giải quyết bằng cách khác: Đó là về các hệ thống nhúng trong đó cách duy nhất để kích hoạt thiết lập lại là đóng băng thiết bị để cơ quan giám sát tự động khởi động lại.

Nếu bạn biết bất kỳ ví dụ hợp lệ / tốt nào mà bạn cần một vòng lặp vô hạn không có gì, xin vui lòng cho tôi biết.


1
Ví dụ về nơi bạn có thể muốn một vòng lặp vô hạn: một hệ thống nhúng mà bạn không muốn ngủ vì lý do hiệu suất và tất cả các mã bị treo một hoặc hai ngắt?
JCx

@JCx trong Tiêu chuẩn C, các ngắt nên đặt cờ kiểm tra vòng lặp chính, do đó vòng lặp chính sẽ có hành vi quan sát được trong trường hợp các cờ được đặt. Chạy mã đáng kể trong các ngắt là không di động.
MM

-1

Tôi nghĩ rằng đáng để chỉ ra rằng các vòng lặp sẽ là vô hạn ngoại trừ việc chúng tương tác với các luồng khác thông qua các biến không đồng bộ, không đồng bộ hóa giờ đây có thể mang lại hành vi không chính xác với trình biên dịch mới.

Tôi nói cách khác, làm cho toàn cầu của bạn biến động - cũng như các đối số được truyền vào một vòng lặp như vậy thông qua con trỏ / tham chiếu.


Nếu chúng tương tác với các luồng khác, bạn không nên biến chúng thành biến động, biến chúng thành nguyên tử hoặc bảo vệ chúng bằng khóa.
BCoates

1
Thjs là lời khuyên khủng khiếp. Làm cho chúng volatilelà không cần thiết cũng không phải là gây khó chịu, và nó làm tổn thương đáng kể hiệu suất.
David Schwartz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.