2017-05-18 17 views
20

Tôi có một vĩ mô được sử dụng trên tất cả các mã của tôi rằng trong chế độ gỡ lỗi không:Làm cách nào để hướng dẫn tối ưu hóa GCC dựa trên các xác nhận mà không có chi phí thời gian chạy?

#define contract(condition) \ 
    if (!(condition)) \ 
     throw exception("a contract has been violated"); 

... nhưng trong chế độ phát hành:

#define contract(condition) \ 
    if (!(condition)) \ 
     __builtin_unreachable(); 

Điều này không hơn một assert() là, trong phiên bản xây dựng, trình biên dịch có thể tối ưu hóa rất nhiều mã nhờ sự truyền bá UB.

Ví dụ, thử nghiệm với đoạn mã sau:

int foo(int i) { 
    contract(i == 1); 
    return i; 
} 

// ... 

foo(0); 

... ném một ngoại lệ trong chế độ gỡ lỗi, nhưng sản xuất lắp ráp cho một vô điều kiện return 1; trong chế độ phát hành:

foo(int): 
     mov  eax, 1 
     ret 

Điều kiện và mọi thứ phụ thuộc vào nó, đã được tối ưu hóa.

Sự cố của tôi nảy sinh với các điều kiện phức tạp hơn. Khi trình biên dịch không thể chứng minh rằng điều kiện không có tác dụng phụ, nó không tối ưu hóa nó, đó là một hình phạt runtme so với việc không sử dụng hợp đồng.

Có cách nào để thể hiện rằng điều kiện trong hợp đồng không có tác dụng phụ, do đó, luôn là được tối ưu hóa?

+0

Nhận xét không cho thảo luận mở rộng; cuộc hội thoại này đã được [chuyển sang trò chuyện] (http://chat.stackoverflow.com/rooms/144609/discussion-on-question-by-lyingonthesky-how-to-guide-gcc-optimizations-based-on). – deceze

Trả lời

2

Không có cách nào để buộc tối ưu hóa mã như - nếu nó là mã chết, vì GCC phải luôn luôn khiếu nại với tiêu chuẩn.

Mặt khác, biểu thức có thể được kiểm tra để không có bất kỳ tác dụng phụ nào bằng cách sử dụng thuộc tính error sẽ hiển thị lỗi bất cứ khi nào cuộc gọi của hàm không thể được tối ưu hóa.

Một ví dụ về một macro để kiểm tra bất cứ điều gì được tối ưu hóa ra ngoài và không UB tuyên truyền:

#define _contract(condition) \ 
    { 
     ([&]() __attribute__ ((noinline,error ("contract could not be optimized out"))) { 
      if (condition) {} // using the condition in if seem to hide `unused` warnings. 
     }()); 
     if (!(condition)) 
      __builtin_unreachable(); 
    } 

Thuộc tính lỗi không hoạt động mà không cần tối ưu hóa (để macro này chỉ có thể được sử dụng để giải phóng \ chế độ tối ưu hóa biên soạn) . Lưu ý rằng lỗi cho biết bất kỳ hợp đồng nào có tác dụng phụ được hiển thị trong quá trình liên kết.

A test that shows an error with unoptimizable contract.

A test that optimizes out a contract but, does UB propagation with it.

5

Vì vậy, không phải là câu trả lời, nhưng một số kết quả thú vị có thể dẫn đến đâu đó.

tôi đã kết thúc với mã đồ chơi sau:

#define contract(x) \ 
    if (![&]() __attribute__((pure, noinline)) { return (x); }()) \ 
     __builtin_unreachable(); 

bool noSideEffect(int i); 

int foo(int i) { 
    contract(noSideEffect(i)); 

    contract(i == 1); 

    return i; 
} 

Bạn có thể follow along at home, quá;)

noSideEffect là chức năng mà chúng ta biết không có tác dụng phụ, nhưng trình biên dịch thì không.
Nó đã đi như thế này:

  1. GCC có __attribute__((pure)) để đánh dấu một chức năng như không có tác dụng phụ.

  2. Đủ điều kiện noSideEffect với thuộc tính pure xóa hoàn toàn cuộc gọi chức năng. Tốt đẹp!

  3. Nhưng chúng tôi không thể sửa đổi tuyên bố của noSideEffect. Vì vậy, làm thế nào về gói cuộc gọi của nó bên trong một chức năng đó là chính nó pure? Và vì chúng ta đang cố gắng tạo ra một vĩ mô khép kín, một lambda có vẻ tốt.

  4. Đáng ngạc nhiên, điều đó không có tác dụng ... trừ khi chúng tôi thêm noinline vào lambda! Tôi cho rằng trình tối ưu hóa inlines lambda đầu tiên, mất thuộc tính pure trên đường đi, trước khi xem xét tối ưu hóa cuộc gọi đến noSideEffect. Với noinline, theo cách hơi trực quan, trình tối ưu hóa có thể xóa sạch mọi thứ. Tuyệt quá!

  5. Tuy nhiên, hiện có hai vấn đề: với thuộc tính noinline, trình biên dịch tạo ra nội dung cho mỗi lambda, ngay cả khi chúng không bao giờ được sử dụng. Meh - các mối liên kết có lẽ sẽ có thể ném chúng đi anyway.
    Nhưng quan trọng hơn ... Chúng tôi thực sự mất đi tối ưu mà __builtin_unreachable() đã kích hoạt :(

Nói tóm lại, bạn có thể loại bỏ hoặc đặt lại noinline trong đoạn mã trên, và kết thúc với một trong các Kết quả:

Với noinline

; Unused code 
foo(int)::{lambda()#2}::operator()() const: 
     mov  rax, QWORD PTR [rdi] 
     cmp  DWORD PTR [rax], 1 
     sete al 
     ret 
foo(int)::{lambda()#1}::operator()() const: 
     mov  rax, QWORD PTR [rdi] 
     mov  edi, DWORD PTR [rax] 
     jmp  noSideEffect(int) 

; No function call, but the access to i is performed 
foo(int): 
     mov  eax, edi 
     ret 

Without noinline

; No unused code 
; Access to i has been optimized out, 
; but the call to `noSideEffect` has been kept. 
foo(int): 
     sub  rsp, 8 
     call noSideEffect(int) 
     mov  eax, 1 
     add  rsp, 8 
     ret 
+1

Tôi chơi với đồ chơi này khá một chút, tôi nghĩ rằng đó là một lỗi trong GCC rằng nó tách thuộc tính 'pure' khi nó inlining nó, cảm ơn cho các kiến ​​thức có anyway. – LyingOnTheSky

4

Có một cách để thể hiện rằng các điều kiện trong hợp đồng không có tác dụng phụ, do đó nó luôn luôn được tối ưu hóa ra?

Không có khả năng.

Được biết rằng bạn không thể lấy một bộ sưu tập lớn các xác nhận, biến chúng thành các giả định (qua __builtin_unreachable) và mong đợi kết quả tốt (ví dụ: Assertions Are Pessimistic, Assumptions Are Optimistic bởi John Regehr).

Một số manh mối:

  • Clang, trong khi đã có __builtin_unreachable nội tại, giới thiệu __builtin_assume chính xác cho mục đích này.

  • N4425 - Generalized Dynamic Assumptions(*) ghi chú rằng:

    GCC không cung cấp một cách rõ ràng một cơ sở giả định chung, nhưng giả định chung có thể được mã hóa bằng cách sử dụng sự kết hợp của dòng điều khiển và __builtin_unreachable nội

    ...

    Triển khai hiện tại cung cấp các giả định chung sử dụng một số từ khóa trong không gian định danh được triển khai (__assume, __builtin_assume, v.v.). Vì đối số biểu thức không được đánh giá (các hiệu ứng phụ bị loại bỏ), việc chỉ định điều này theo chức năng thư viện đặc biệt (ví dụ: std::assume) có vẻ khó khăn.

  • Thư viện Hướng Dẫn Cấp Dưỡng (GSL, được tổ chức bởi Microsoft, nhưng không có cách nào Microsoft cụ thể) có "chỉ đơn thuần là" mã này:

    #ifdef _MSC_VER 
    #define GSL_ASSUME(cond) __assume(cond) 
    #elif defined(__clang__) 
    #define GSL_ASSUME(cond) __builtin_assume(cond) 
    #elif defined(__GNUC__) 
    #define GSL_ASSUME(cond) ((cond) ? static_cast<void>(0) : __builtin_unreachable()) 
    #else 
    #define GSL_ASSUME(cond) static_cast<void>(!!(cond)) 
    #endif 
    

    và nhấn mạnh rằng:

    // GSL_ASSUME(cond) 
    // 
    // Tell the optimizer that the predicate cond must hold. It is unspecified 
    // whether or not cond is actually evaluated. 
    

*) Giấy rejected: Hướng dẫn của EWG là cung cấp chức năng trong các cơ sở hợp đồng được đề xuất.

+0

Tôi có thể biết "cond" là gì và nó được khai báo ở đâu? –

+1

@ jack_1729 'cond' là tham số đầu vào của macro giống như hàm (xem https://gcc.gnu.org/onlinedocs/cpp/Macro-Arguments.html). 'GSL_ASSUME' mong đợi một biểu thức/điều kiện boolean (' cond'). – manlio

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