2017-10-27 47 views
6

Tôi đã tối ưu hóa mã C++ trong đó tôi gặp phải một tình huống có thể được đơn giản hóa như sau.hiệu ứng tối ưu hóa gcc trên các vòng có biến cố định rõ ràng

xem xét mã này:

#include <iostream> 
#include <thread> 

using namespace std; 

bool hit = false; 

void F() 
{ 
    this_thread::sleep_for(chrono::seconds(1)); 
    hit = true; 
} 

int main() 
{ 
    thread t(F); 

    while (!hit) 
     ; 

    cout << "finished" << endl; 
    t.join(); 
    return 0; 
} 

này về cơ bản bắt đầu một chủ đề mà sau một giây sẽ thay đổi giá trị của hit để true. Đồng thời mã sẽ nhập một vòng trống sẽ tiếp tục cho đến khi giá trị của hit trở thành true. Tôi biên soạn điều này với gcc-5.4 sử dụng cờ -g và mọi thứ đều ổn. Mã sẽ xuất ra finished và kết thúc. Nhưng sau đó tôi biên dịch nó với cờ -O2 và lần này mã đã bị mắc kẹt trong vòng lặp vô hạn.

Nhìn vào tháo gỡ, trình biên dịch đã được tạo ra sau, đó là nguyên nhân gốc rễ của vòng lặp vô hạn:

jmp 0x6ba6f3! 0x00000000006ba6f3

OK, vì vậy rõ ràng, trình biên dịch đã suy luận rằng giá trị hit 's là false và nó sẽ không thay đổi trong vòng lặp vậy tại sao không cho rằng nó là một vòng lặp vô hạn mà không xem xét rằng thread khác có thể thay đổi giá trị của nó ! Và chế độ tối ưu hóa này được thêm ở cấp cao hơn (-O2). Vì tôi không chính xác là chuyên gia về cờ tối ưu hóa, bất cứ ai có thể cho tôi biết ai trong số họ chịu trách nhiệm về kết quả này để tôi có thể tắt nó đi? Và sẽ tắt nó có bất kỳ chi phí hiệu suất lớn cho các phần mã khác? Ý tôi là, mẫu mã này hiếm đến mức nào?

+1

sử dụng 'std :: nguyên tử '. – Jarod42

+2

Điều gì xảy ra nếu bạn khai báo 'hit' là dễ bay hơi? – Milack27

+0

@ Milack27 có nó giải quyết được vấn đề! Người đàn ông, có rất nhiều điều trong c + + mà tôi vẫn không biết! – Sinapse

Trả lời

6

Mã này có hành vi không xác định. Bạn đang sửa đổi hit từ một chuỗi và đọc nó thành một chuỗi khác mà không cần đồng bộ hóa.

Tối ưu hóa hit thành false là kết quả hợp lệ của Hành vi không xác định. Bạn có thể giải quyết vấn đề này bằng cách thực hiện hit a std::atomic<bool>. Điều này làm cho nếu được xác định rõ ràng và chặn tối ưu hóa.

+0

Có điều này cũng sẽ giải quyết vấn đề (tôi nghĩ rằng dễ bay hơi sẽ làm điều đó hiệu quả hơn cho trường hợp của tôi) nhưng OMG !! Chỉ cần làm thế nào trình biên dịch biết nó khi bạn xác định biến là nguyên tử ?! Tôi có nghĩa là dễ bay hơi có ý nghĩa kể từ khi bạn đang thêm một vòng loại nhưng trong trường hợp của std :: nguyên tử, làm thế nào trình biên dịch sẽ suy ra rằng nó có thể thay đổi trong một thread ?? – Sinapse

+0

Đoán xem! Có các thành viên dễ bay hơi bên trong lớp 'std :: atomic '. :) Thử mở tệp "nguyên tử" từ g ++ bao gồm thư mục, bạn có thể thấy chúng ở đó. – Milack27

+0

Đẹp, điều đó xứng đáng! – Sinapse

2

Nếu bạn muốn đọc/ghi hit từ một số chủ đề cùng một lúc thì bạn cần một số loại đồng bộ hóa nếu không bạn sẽ giới thiệu một điều kiện chủng tộc. Bạn có thể tạo hit một std::atomic<bool> hoặc thêm mutex cần được khóa khi truy cập vào giá trị hit. Nếu bạn chỉ muốn đợi chuỗi kết thúc công việc của mình thì bạn có thể để lại chỉ thread.join() (và in "xong" sau nó) mà không cần thêm bất kỳ cờ bổ sung nào.

1

Bằng cách tuyên bố hit là dễ bay hơi, bạn đang nói với trình biên dịch rằng biến này có thể được sửa đổi bởi các yếu tố bên ngoài bất kỳ lúc nào, do đó trình biên dịch sẽ không giả định giá trị của nó sẽ không thay đổi theo chức năng main của bạn.

Miễn là chỉ có một chuỗi ghi vào biến số hit, mã của bạn sẽ hoạt động bình thường, không có điều kiện chủng tộc nào liên quan. Tuy nhiên, khi bạn đang xử lý nhiều luồng, nó luôn an toàn hơn khi sử dụng các công cụ đồng bộ hóa, như các đối tượng nguyên tử, các mutex và các semaphores, như đã đề cập trong các câu trả lời khác ở đây.

+0

'volatile' [không cung cấp thứ tự cần thiết] (https://stackoverflow.com/a/4558031/15416). – MSalters

+0

Có. Như tôi đã nói, _it luôn an toàn hơn khi sử dụng các công cụ đồng bộ hóa_. Sử dụng 'volatile' sẽ hoạt động đúng _in case_ này. – Milack27