Mã nào tốt hơn cho tối ưu hóa dự đoán chi nhánh?


10

Đưa ra dự đoán nhánh, và cũng là hiệu quả của tối ưu hóa trình biên dịch, mã nào có xu hướng cung cấp hiệu suất vượt trội?

Lưu ý rằng bRareExceptionPftime đại diện cho một điều kiện không phổ biến. Nó không phải là con đường logic thông thường.

/* MOST COMMON path must branch around IF clause */

bool SomeFunction(bool bRareExceptionPresent)
{
  // abort before function
  if(bRareExceptionPresent)
  {
     return false;
  }    
  .. function primary body ..    
  return true;
}

/* MOST COMMON path does NOT branch */

bool SomeFunction(bool bRareExceptionPresent)
{
  if(!bRareExceptionPresent)
  {
    .. function primary body ..
  }
  else
  {
    return false;
  }
  return true;
}

9
Tôi sẽ đi ra ngoài trên một chi ở đây và nói rằng không có sự khác biệt nào.
Robert Harvey

7
Điều này có thể phụ thuộc vào CPU cụ thể mà bạn đang biên dịch, vì chúng có các kiến ​​trúc đường ống khác nhau (khe trễ so với khe trễ). Thời gian bạn dành để nghĩ về điều này có thể nhiều hơn thời gian tiết kiệm khi chạy - hồ sơ trước, sau đó tối ưu hóa.

2
Nó gần như chắc chắn là tối ưu hóa vi mô sớm.
Robert Harvey

2
@MichaelT Yep, profiling thực sự là cách đáng tin cậy duy nhất để biết những gì đang thực sự xảy ra với hiệu suất cho mã trên mục tiêu, nền tảng, trong bối cảnh của nó. Tuy nhiên, tôi đã tò mò liệu một người thường được ưa thích.
dyasta

1
@RobertHarvey: Đó là tối ưu hóa vi mô sớm, ngoại trừ trong trường hợp cả hai điều kiện được đáp ứng: (1) vòng lặp được gọi là hàng tỷ (không phải hàng triệu) lần; và (2) trớ trêu thay, khi thân vòng lặp nhỏ xíu về mã máy. Điều kiện số 2 có nghĩa là phần nhỏ thời gian dành cho chi phí không đáng kể so với thời gian dành cho công việc hữu ích. Tin tốt là thông thường, trong những tình huống như vậy khi cả hai điều kiện được đáp ứng, SIMD (vectorization), về bản chất là không phân nhánh, sẽ giải quyết tất cả các vấn đề về hiệu suất.
rwong

Câu trả lời:


10

Trong thế giới ngày nay, nó không quan trọng lắm, nếu có.

Dự đoán nhánh động (một điều gì đó đã được suy nghĩ trong nhiều thập kỷ (xem Phân tích về khối lượng công việc của Hệ thống dự đoán nhánh động được công bố năm 1996)) là khá phổ biến.

Một ví dụ về điều này có thể được tìm thấy trong bộ xử lý ARM. Từ Trung tâm Thông tin Arm về Dự đoán Chi nhánh

Để cải thiện độ chính xác dự đoán nhánh, kết hợp các kỹ thuật tĩnh và động được sử dụng.

Câu hỏi sau đó là "dự đoán nhánh động trong bộ xử lý cánh tay là gì?" Đọc tiếp theo dự đoán nhánh động cho thấy rằng nó sử dụng sơ đồ dự đoán 2 bit (được mô tả trong bài viết) xây dựng thông tin về việc nhánh đó được lấy mạnh hay yếu hoặc không lấy.

Theo thời gian (và theo thời gian, ý tôi là một vài lần đi qua khối đó), điều này sẽ xây dựng thông tin theo cách mà mã sẽ đi.

Đối với dự đoán tĩnh , nó xem xét cách mã nhìn chính nó và cách nhánh được thực hiện trong bài kiểm tra - theo hướng dẫn trước đó hoặc một trong mã nữa:

Sơ đồ được sử dụng trong bộ xử lý ARM1136JF-S dự đoán rằng tất cả các nhánh có điều kiện chuyển tiếp không được thực hiện và tất cả các nhánh lùi được thực hiện. Khoảng 65% của tất cả các chi nhánh được đi trước bởi các chu kỳ không phân nhánh đủ để được dự đoán hoàn toàn.

Như Sparky đã đề cập, điều này dựa trên sự hiểu biết rằng các vòng lặp thường xuyên hơn không, vòng lặp. Các nhánh vòng lặp ngược (nó có một nhánh ở cuối vòng lặp để khởi động lại nó ở trên cùng) - nó thường làm điều này.

Nguy hiểm của việc cố gắng đoán thứ hai trình biên dịch là bạn không biết mã đó thực sự sẽ được biên dịch như thế nào (và được tối ưu hóa). Và đối với hầu hết các phần, nó không thành vấn đề. Với dự đoán động, hai lần thông qua chức năng, nó sẽ dự đoán bỏ qua câu lệnh bảo vệ để trả về sớm. Nếu hiệu suất của hai đường ống xả là hiệu suất quan trọng, có những điều khác phải lo lắng.

Thời gian để đọc một kiểu này so với kiểu kia có thể có tầm quan trọng lớn hơn - làm cho mã sạch để con người có thể đọc nó, bởi vì trình biên dịch sẽ hoạt động tốt dù bạn có viết mã lộn xộn hay lý tưởng hóa như thế nào.


7
Một câu hỏi stackoverflow nổi tiếng cho thấy dự đoán chi nhánh vấn đề, ngay cả ngày nay.
Florian Margaine

3
@FlorianMargaine trong khi nó có vấn đề, nó gặp phải tình huống thực sự có vấn đề đòi hỏi sự hiểu biết về những gì bạn đang biên dịch và cách thức hoạt động (arm vs x86 vs mips ...). Viết mã cố gắng thực hiện tối ưu hóa vi mô này khi bắt đầu có khả năng làm việc từ các cơ sở nhầm lẫn và không đạt được hiệu quả mong muốn.

Tất nhiên, chúng ta đừng trích dẫn DK. Nhưng tôi nghĩ rằng câu hỏi này rõ ràng là theo nghĩa tối ưu hóa, khi bạn đã trải qua giai đoạn định hình. :-)
Florian Margaine

2
@MichaelT Câu trả lời hay, và tôi rất đồng ý với kết luận của bạn. Loại tối ưu hóa sơ lược / trừu tượng này chắc chắn có thể phản tác dụng. Nó kết thúc là một trò chơi đoán, khiến người ta đưa ra quyết định thiết kế vì những lý do không hợp lý. Tuy nhiên, tôi thấy mình tò mò; o
dyasta


9

Tôi hiểu rằng lần đầu tiên CPU gặp một nhánh, nó sẽ dự đoán (nếu được hỗ trợ) rằng các nhánh chuyển tiếp không được lấy và các nhánh ngược là. Lý do cho điều này là các vòng lặp (thường phân nhánh ngược) được giả định là được thực hiện.

Trên một số bộ xử lý, bạn có thể đưa ra gợi ý trong hướng dẫn lắp ráp xem đường dẫn nào có nhiều khả năng hơn. Chi tiết về điều này thoát khỏi tôi vào lúc này.

Ngoài ra, một số trình biên dịch C cũng hỗ trợ dự đoán nhánh tĩnh để bạn có thể cho trình biên dịch biết nhánh nào có khả năng hơn. Đổi lại, nó có thể sắp xếp lại mã được tạo hoặc sử dụng các hướng dẫn đã sửa đổi để tận dụng thông tin này (hoặc thậm chí bỏ qua việc bỏ qua nó).

__builtin_expect((long)!!(x), 1L)  /* GNU C to indicate that <x> will likely be TRUE */
__builtin_expect((long)!!(x), 0L)  /* GNU C to indicate that <x> will likely be FALSE */

Hi vọng điêu nay co ich.


3
"Sự hiểu biết của tôi là lần đầu tiên CPU gặp một nhánh, nó sẽ dự đoán (nếu được hỗ trợ) rằng các nhánh chuyển tiếp không được lấy và các nhánh ngược là". Đây là một suy nghĩ rất thú vị. Bạn có bất cứ bằng chứng nào cho thấy điều này thực sự được thực hiện trong các kiến ​​trúc phổ biến không?
blubb

5
Thẳng từ miệng ngựa: Một nhánh phía trước mặc định không được lấy. Một nhánh lạc hậu mặc định để thực hiện . Và từ cùng một trang: "tiền tố 0x3E - dự đoán tĩnh một nhánh như đã thực hiện".
MSalters

Có một nền tảng bất khả tri pragma tương đương với __builtin_expect?
MarcusJ
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.