Caching (ví dụ branch target caching), đơn vị song song tải (một phần của pipelining, mà còn những thứ như "hit dưới bỏ lỡ" mà không trì hoãn các đường ống dẫn), và out-of-order execution có nhiều khả năng để giúp biến đổi một load
-load
- branch
vào cái gì đó gần với một số branch
cố định. Hướng dẫn gấp/loại bỏ (thuật ngữ thích hợp cho điều này là gì?) Trong giai đoạn giải mã hoặc dự đoán nhánh của đường ống cũng có thể đóng góp. Tuy nhiên, tất cả điều này phụ thuộc vào rất nhiều thứ khác nhau: có bao nhiêu mục tiêu chi nhánh khác nhau (ví dụ: bạn có thể kích hoạt nhiều quá tải ảo khác nhau), bao nhiêu thứ bạn lặp lại (là bộ nhớ cache mục tiêu chi nhánh). "ấm"? làm thế nào về icache/dcache?), làm thế nào các bảng ảo hoặc bảng indirection được đặt ra trong bộ nhớ (là họ thân thiện với bộ nhớ cache, hoặc là mỗi tải vtable mới có thể đuổi một vtable cũ?), là bộ nhớ cache được đã bị vô hiệu nhiều lần do ping-ponging đa lõi, v.v ...
(Disclaimer: Tôi chắc chắn không phải là chuyên gia ở đây, và rất nhiều kiến thức của tôi đến từ việc nghiên cứu bộ vi xử lý nhúng theo thứ tự) Nếu bạn có chỉnh sửa, hãy bình luận!)
Cách chính xác để xác định xem đó có phải là vấn đề đối với một chương trình cụ thể hay không là tất nhiên đối với hồ sơ. Nếu bạn có thể, làm như vậy với sự trợ giúp của bộ đếm phần cứng - họ có thể cho bạn biết rất nhiều về những gì đang xảy ra trong các giai đoạn khác nhau của đường ống.
Edit:
Như Hans passant chỉ ra trong một bình luận trên Modern CPU Inner Loop Indirection Optimizations, chìa khóa để nhận được hai điều này để có cùng một lượng thời gian là khả năng một cách hiệu quả "nghỉ hưu" nhiều hơn một hướng dẫn mỗi chu kỳ. Hướng dẫn loại bỏ có thể giúp với điều này, nhưng superscalar design có lẽ là quan trọng hơn (nhấn dưới bỏ lỡ là một ví dụ rất nhỏ và cụ thể, đơn vị tải đầy đủ dự phòng có thể là một tốt hơn).
Hãy lấy một tình huống lý tưởng, và giả sử một chi nhánh trực tiếp chỉ là một hướng dẫn:
branch dest
... và một chi nhánh gián tiếp là ba (có thể bạn có thể lấy nó ra làm đôi, nhưng nó lớn hơn một):
load vtable from this
load dest from vtable
branch dest
giả sử một tình huống hoàn toàn hoàn hảo: * này và toàn bộ vtable là trong bộ nhớ cache L1, L1 Cache là đủ nhanh để hỗ trợ khấu hao theo một chu kỳ mỗi chi phí hướng dẫn cho hai tải. (Bạn thậm chí có thể giả định bộ xử lý sắp xếp lại các tải và trộn chúng với các lệnh trước đó để cho phép thời gian để chúng hoàn thành trước nhánh; nó không quan trọng đối với ví dụ này.) Giả sử bộ nhớ cache mục tiêu là nóng, và không có đường dẫn chi phí tuôn ra cho chi nhánh và lệnh chi nhánh đi xuống một chu kỳ đơn (khấu hao).
Thời gian lý thuyết tối thiểu cho ví dụ đầu tiên là 1 chu kỳ (khấu hao).
Lý thuyết tối thiểu cho ví dụ thứ hai, loại bỏ chỉ dẫn vắng mặt hoặc các đơn vị chức năng dự phòng hoặc thứ gì đó sẽ cho phép gỡ bỏ nhiều lệnh trên mỗi chu kỳ, là 3 chu kỳ (có 3 hướng dẫn)!
Tải gián tiếp sẽ luôn chậm hơn, vì có nhiều hướng dẫn hơn, cho đến khi bạn tiếp cận với thiết bị siêu âm cho phép gỡ bỏ nhiều lệnh trên mỗi chu kỳ.
Khi bạn có điều này, mức tối thiểu cho cả hai ví dụ sẽ trở thành cái gì đó giữa 0 và 1 chu kỳ, một lần nữa, với điều kiện mọi thứ khác là lý tưởng. Có thể cho rằng bạn phải có nhiều hoàn cảnh lý tưởng hơn cho ví dụ thứ hai để đạt được mức tối thiểu lý thuyết đó so với ví dụ đầu tiên, nhưng bây giờ có thể.
Trong một số trường hợp bạn quan tâm, có thể bạn sẽ không đạt đến mức tối thiểu đó cho một trong hai ví dụ. Bộ nhớ cache mục tiêu chi nhánh sẽ bị lạnh hoặc vtable sẽ không nằm trong bộ đệm dữ liệu hoặc máy sẽ không có khả năng sắp xếp lại hướng dẫn để tận dụng tối đa các đơn vị chức năng dự phòng.
... đây là nơi mà tiểu sử xuất hiện, thường là một ý tưởng hay.
Bạn có thể chỉ espouse một hoang tưởng nhẹ về ảo ở nơi đầu tiên. Xem Noel Llopis's article on data oriented design, xuất sắc Pitfalls of Object-Oriented Programming slides và Mike Acton's grumpy-yet-educational presentations. Bây giờ bạn đã đột nhiên chuyển sang các mẫu mà CPU đã có thể hài lòng với, nếu bạn đang xử lý nhiều dữ liệu.
Tính năng ngôn ngữ cấp cao như ảo thường là sự cân bằng giữa tính biểu cảm và kiểm soát. Mặc dù vậy, tôi thực sự nghĩ rằng bằng cách tăng nhận thức của bạn về những gì ảo thực sự đang làm (đừng ngại đọc chế độ xem tháo gỡ theo thời gian, và chắc chắn xem qua hướng dẫn sử dụng kiến trúc của CPU), bạn sẽ có xu hướng sử dụng nó khi nó có ý nghĩa và không khi nó không, và một profiler có thể bao gồm phần còn lại nếu cần thiết.
Tuyên bố một kích thước phù hợp với tất cả về "không sử dụng ảo" hoặc "sử dụng ảo không có khả năng tạo sự khác biệt có thể đo lường" khiến tôi khó chịu. Thực tế thường phức tạp hơn, và bạn sẽ ở trong tình huống mà bạn quan tâm đủ để hồ sơ hoặc tránh nó, hoặc bạn ở 95% kia, nơi nó có thể không đáng quan tâm ngoại trừ nội dung giáo dục có thể.
Cảm ơn câu trả lời chi tiết. Tôi đã xem các liên kết trên "Thiết kế định hướng dữ liệu" và "Những cạm bẫy của OOP" nhưng tôi nghĩ có nhiều hơn những gì được nói. Điều này đòi hỏi nhiều công việc hơn, nhưng ban đầu tôi nghĩ rằng bố trí bộ nhớ cho hiệu quả bộ nhớ cache nên được xử lý bởi một lớp cơ bản mà không bị mất "thiết kế thông thường" cho hiệu quả. –
Tôi nghĩ rằng tất cả phụ thuộc vào những gì bạn xem xét "thiết kế thông thường". =) Có lẽ tôi đã ở trong ngành công nghiệp gamedev quá lâu; nó thực sự là tất cả về dữ liệu ở đây. Bắt đầu xem xét các hệ thống khi di chuyển dữ liệu xung quanh và xử lý nó hiệu quả đôi khi làm cho thiết kế hệ thống dễ dàng hơn. Lặn vào khái niệm ngôn ngữ chức năng, loại chuyển đổi này dường như không xa tầm tay. – leander