2008-11-12 14 views
9

Tôi đang sử dụng mô-đun ghi nhật ký có thể bật/tắt báo cáo khi chạy. Các cuộc gọi thường diễn ra như sau:Tối ưu hóa trình biên dịch C++ của các đối số đã qua

WARN(
    "Danger Will Robinson! There are " 
    + boost::lexical_cast<string>(minutes) 
    + " minutes of oxygen left!" 
); 

Tôi đang sử dụng chức năng nội tuyến cho WARN, nhưng tôi tò mò về mức độ tối ưu hóa của các đối số trong toàn bộ chương trình tốn kém. Chức năng WARN đi một cái gì đó như thế này:

bool WARNINGS_ENABLED = false; 
inline void WARN(const string &message) { 
    if (!WARNINGS_ENABLED) { 
     return; 
    } 
    // ... 
} 

Cho rằng xây dựng các đối số chuỗi không có tác dụng phụ, trình biên dịch sẽ tối ưu hóa nó ra? Yêu cầu tối ưu hóa mức độ nhất định (-Ox trong số g++ cho một số x)?

Trả lời

12

Nếu bạn cần có khả năng bật và tắt có chọn lọc cảnh báo trong thời gian chạy, trình biên dịch sẽ không có thể tối ưu hóa cuộc gọi.

gì bạn cần là để đổi tên chức năng bạn-WARN2 và thêm một điều gì đó vĩ mô như:

#define WARN(s) do {if (WARNINGS_ENABLED) WARN2(s);} while (false) 

này sẽ ngăn chặn việc đánh giá s tại thời gian chạy trừ khi bạn có những cảnh báo được kích hoạt.

Công cụ trong khi là một thủ thuật cho phép nó được sử dụng ở bất kỳ nơi nào trong mã (câu lệnh trần truồng, câu lệnh trong câu lệnh if-block, được đặt trong một câu lệnh if-block, braced và unbraced while unbraced.).

+0

Điều đó sẽ hoạt động tốt. –

+0

Đúng - một tình huống rõ ràng khi macro cần được sử dụng trên các hàm nội dòng. – cdleary

+0

+1 nhưng điều này thực sự là: #define WARN (s) làm {if (WARNINGS_ENABLED) WARN2 (s); } trong khi (false) –

1

Tôi đoán rằng nó chỉ có cơ hội tối ưu hóa nó nếu nó có thể chứng minh rằng không có tác dụng phụ (có thể khó cho trình biên dịch thực hiện cho một cuộc gọi hàm đắt tiền).

Tôi không phải là chuyên gia tăng cường, nhưng tôi đoán có cách để xây dựng một lambda sẽ chỉ được đánh giá để tạo chuỗi nếu WARNINGS_ENABLED là đúng. Một cái gì đó giống như ...

inline void warnFunc(some_boost_lambda &message_generator) { 
    if (WARNINGS_ENABLED) { 
    cerr << message_generator() << endl; 
    } 
} 

#define WARN(msg) warnFunc(...insert boost magic here to turn msg into a lambda...) 
+0

Ý tưởng thú vị! – cdleary

+0

vâng ý tưởng của bạn hoạt động tốt. chỉ cần thử nghiệm nó với tăng :: lambda (không có chuyên gia trong nó, hoặc, nhưng nó đã thẳng về phía trước) –

+0

yeah ý tưởng của bạn hoạt động tuyệt vời. chỉ cần thử nghiệm nó với boost :: lambda (không có chuyên gia trong nó, hoặc, nhưng nó đã được thẳng về phía trước). Đây là mẫu có đầu ra: http://codepad.org/PmUh7AHj –

6

Bạn có thể kiểm tra những gì GCC/G ++ làm bằng cách sử dụng tùy chọn -S. Điều này sẽ xuất mã trước khi nó thực sự được lắp ráp - xem gcc(1).

GCC và G ++ nhiều hoặc ít hành xử giống nhau trong trường hợp này. Vì vậy, đầu tiên tôi dịch mã vào C để thực hiện một số xét nghiệm chuyên sâu:

char WARNINGS_ENABLED = 0; 

inline void WARN(const char* message) { 
    if (!WARNINGS_ENABLED) { 
     return; 
    } 
    puts(message); 
} 

int main() { 
    WARN("foo"); 
    return 0; 
} 

chạy gcc-O3 -S file.c và nhìn vào các tập tin đầu ra 'file.s'
Bạn sẽ thấy rằng GCC không xóa bất cứ điều gì!

Đó không phải là những gì bạn yêu cầu, nhưng để cung cấp cho trình biên dịch cơ hội tối ưu hóa mã đó, bạn sẽ phải thực hiện WARNINGS_ENABLED hằng số. Một cách khác là đặt số tĩnh và không thay đổi giá trị trong tệp đó. Nhưng: làm cho nó tĩnh có tác dụng phụ là biểu tượng sẽ không được xuất.

static const char WARNINGS_ENABLED = 0; 

inline void WARN(const char* message) { 
    if (!WARNINGS_ENABLED) { 
     return; 
    } 
    puts(message); 
} 

int main() { 
    WARN("foo"); 
    return 0; 
} 

GCC sau đó dọn dẹp hoàn toàn mã.

+0

Câu trả lời rất hay! Thật không may, hành vi đăng nhập phải có khả năng bị thao túng trong thời gian chạy. – cdleary

0

Bạn không thể xác định toàn bộ điều đó bằng cách sử dụng bộ tiền xử lý?

void inline void LogWarning(const string &message) 
{ 
    //Warning 
} 

#ifdef WARNINGS_ENABLED 
#define WARN(a) LogWarning(a) 
#else 
#define WARN(a) 
#endif 

Đây chỉ là cách mà macro ASSERT() hoạt động. Tất cả mã bên trong dấu ngoặc đơn trong WARN thậm chí không làm cho nó thông qua bộ tiền xử lý cho trình biên dịch. Điều đó có nghĩa là bạn có thể thực hiện các công cụ khác như

#ifdef WARNINGS_ENABLED 
// Extra setup for warning 
#endif 
//.... 
WARN(uses setup variables) 

Và nó sẽ biên dịch cả hai cách.

Để nhận được trình tối ưu hóa nhận ra rằng không có tác dụng phụ trong dấu ngoặc đơn, bạn có thể đặt một số câu lệnh khá phức tạp trong đó (ví dụ: thao tác chuỗi mức cao).

+0

Nó phải có khả năng chuyển đổi thời gian chạy, nhưng đó chắc chắn sẽ là một giải pháp khả thi thời gian biên dịch. – cdleary

+0

Vì vậy, câu hỏi của bạn là: Cho một hàm X (params) {if (b) {quit}; ...} không chậm trễ của trình biên dịch tính toán params cho đến sau khi nếu? Đó sẽ là ấn tượng. Kể từ khi nhánh là năng động, trình biên dịch là không thể để tối ưu hóa mà ra. –

+0

Vâng, đó là những gì tôi đang hỏi! Ngoại trừ việc trình biên dịch có thể thoát khỏi cuộc gọi và nội tuyến các công cụ trong '...' của bạn. Giống như một số người khác đã chỉ ra, trình biên dịch sẽ cần để có thể chứng minh rằng không có tác dụng phụ bên ngoài trong đi qua các đối số, mà có vẻ khá khó khăn. – cdleary

1

Không, trình biên dịch phải không tối ưu hóa mã trong mọi trường hợp trừ khi báo cáo toàn cầu WARNING_ENABLED được khai báo const.

BTW, nếu WARN là hàm nội dòng, bạn vẫn sẽ trả giá xây dựng thư (điều này rất không hiệu quả trong ví dụ của bạn với lexical_cast và toán tử + trên chuỗi), ngay cả khi nó bị tắt.

Dưới đây là một số chi phí hiệu quả (tối thiểu (gần bằng không với chi phí dự đoán CPU) khi thời gian chạy bị tắt) logging macros hỗ trợ cả ghi nhật ký chức năng và kiểu luồng.