Các phương thức ảo thường được thực hiện thông qua cái gọi là bảng phương thức ảo (vtable viết tắt), trong đó các con trỏ hàm được lưu trữ. Điều này thêm sự gián tiếp vào cuộc gọi thực tế (phải lấy địa chỉ của hàm để gọi từ vtable, sau đó gọi nó - trái ngược với việc chỉ gọi nó ngay trước mặt). Tất nhiên, điều này cần một chút thời gian và một số mã hơn.
Tuy nhiên, nó không nhất thiết là nguyên nhân chính của sự chậm chạp. Vấn đề thực sự là trình biên dịch (thường / thường) không thể biết hàm nào sẽ được gọi. Vì vậy, nó không thể nội tuyến nó hoặc thực hiện bất kỳ tối ưu hóa khác như vậy. Điều này một mình có thể thêm một tá hướng dẫn vô nghĩa (chuẩn bị các thanh ghi, gọi điện, sau đó khôi phục trạng thái sau đó) và có thể ức chế các tối ưu hóa khác, dường như không liên quan. Hơn nữa, nếu bạn phân nhánh như điên bằng cách gọi nhiều triển khai khác nhau, bạn phải chịu những lần truy cập giống như bạn bị phân nhánh như điên thông qua các phương tiện khác: Bộ dự đoán bộ đệm và nhánh sẽ không giúp bạn, các nhánh sẽ mất nhiều thời gian hơn dự đoán hoàn hảo chi nhánh.
Lớn nhưng : Những hit hiệu suất thường quá nhỏ để quan trọng. Chúng đáng để xem xét nếu bạn muốn tạo mã hiệu suất cao và xem xét thêm một chức năng ảo sẽ được gọi với tần suất đáng báo động. Tuy nhiên, cũng nên nhớ rằng việc thay thế các cuộc gọi chức năng ảo bằng các phương tiện phân nhánh khác ( if .. else
,, switch
con trỏ hàm, v.v.) sẽ không giải quyết được vấn đề cơ bản - rất có thể sẽ chậm hơn. Vấn đề (nếu nó tồn tại ở tất cả) không phải là các chức năng ảo mà là (không cần thiết).
Chỉnh sửa: Sự khác biệt trong hướng dẫn cuộc gọi được mô tả trong các câu trả lời khác. Về cơ bản, mã cho một cuộc gọi tĩnh ("bình thường") là:
- Sao chép một số thanh ghi trên ngăn xếp, để cho phép hàm được gọi sử dụng các thanh ghi đó.
- Sao chép các đối số vào các vị trí được xác định trước, để hàm được gọi có thể tìm thấy chúng bất kể nó được gọi từ đâu.
- Đẩy địa chỉ trả lại.
- Nhánh / nhảy tới mã của hàm, là một địa chỉ thời gian biên dịch và do đó được mã hóa trong nhị phân bởi trình biên dịch / liên kết.
- Nhận giá trị trả về từ một vị trí được xác định trước và khôi phục các thanh ghi mà chúng tôi muốn sử dụng.
Một cuộc gọi ảo thực hiện chính xác điều tương tự, ngoại trừ địa chỉ hàm không được biết tại thời điểm biên dịch. Thay vào đó, một vài hướng dẫn ...
- Lấy con trỏ vtable, trỏ đến một mảng các con trỏ hàm (địa chỉ hàm), một cho mỗi hàm ảo, từ đối tượng.
- Lấy đúng địa chỉ hàm từ vtable vào một thanh ghi (chỉ mục nơi địa chỉ hàm chính xác được lưu trữ được quyết định tại thời gian biên dịch).
- Nhảy tới địa chỉ trong thanh ghi đó, thay vì nhảy đến một địa chỉ được mã hóa cứng.
Đối với các nhánh: Một nhánh là bất cứ thứ gì nhảy sang một lệnh khác thay vì chỉ để cho lệnh tiếp theo thực thi. Điều này bao gồm if
, switch
các phần của các vòng lặp khác nhau, các lệnh gọi hàm, v.v. và đôi khi trình biên dịch thực hiện những thứ dường như không phân nhánh theo cách thực sự cần một nhánh dưới mui xe. Xem tại sao xử lý một mảng được sắp xếp nhanh hơn một mảng chưa sắp xếp? tại sao điều này có thể chậm, CPU làm gì để chống lại sự chậm lại này và làm thế nào đây không phải là cách chữa trị.