Chức năng và hiệu suất ảo - C ++


125

Trong thiết kế lớp học của tôi, tôi sử dụng rộng rãi các lớp trừu tượng và các hàm ảo. Tôi có cảm giác rằng các chức năng ảo ảnh hưởng đến hiệu suất. Điều này có đúng không? Nhưng tôi nghĩ sự khác biệt hiệu suất này là không đáng chú ý và có vẻ như tôi đang thực hiện tối ưu hóa sớm. Đúng?


Theo câu trả lời của tôi, tôi khuyên bạn nên đóng cái này dưới dạng trùng lặp của stackoverflow.com/questions/113830
Suma


2
Nếu bạn đang thực hiện tính toán hiệu năng cao và xử lý số, không sử dụng bất kỳ ảo nào trong cốt lõi của tính toán: nó chắc chắn sẽ giết tất cả các hiệu suất và ngăn chặn tối ưu hóa trong thời gian biên dịch. Để khởi tạo hoặc hoàn thiện chương trình, điều đó không quan trọng. Khi làm việc với các giao diện, bạn có thể sử dụng ảo như bạn muốn.
Vincent

Câu trả lời:


90

Một nguyên tắc tốt là:

Đây không phải là vấn đề hiệu suất cho đến khi bạn có thể chứng minh điều đó.

Việc sử dụng các chức năng ảo sẽ có ảnh hưởng rất nhỏ đến hiệu suất, nhưng không có khả năng ảnh hưởng đến hiệu suất chung của ứng dụng của bạn. Những nơi tốt hơn để tìm kiếm các cải tiến hiệu suất là trong các thuật toán và I / O.

Một bài viết xuất sắc nói về các chức năng ảo (và hơn thế nữa) là Con trỏ Chức năng Thành viên và Đại biểu C ++ nhanh nhất có thể .


Còn các hàm ảo thuần túy thì sao? Chúng có ảnh hưởng đến hiệu suất theo bất kỳ cách nào? Chỉ tự hỏi vì dường như họ ở đó chỉ đơn giản là để thực thi.
thomthom

2
@thomthom: Đúng, không có sự khác biệt về hiệu năng giữa các hàm ảo thuần và các hàm ảo thông thường.
Greg Hewgill

168

Câu hỏi của bạn khiến tôi tò mò, vì vậy tôi đã tiếp tục và chạy một số thời gian trên CPU PowerPC theo thứ tự 3GHz mà chúng tôi làm việc cùng. Thử nghiệm tôi đã chạy là tạo một lớp vectơ 4d đơn giản với các hàm get / set

class TestVec 
{
    float x,y,z,w; 
public:
    float GetX() { return x; }
    float SetX(float to) { return x=to; }  // and so on for the other three 
}

Sau đó, tôi thiết lập ba mảng, mỗi mảng chứa 1024 vectơ này (đủ nhỏ để khớp với L1) và chạy một vòng lặp thêm chúng vào nhau (Ax = Bx + Cx) 1000 lần. Tôi chạy này với các chức năng định nghĩa là inline, virtualvà gọi hàm thông thường. Đây là kết quả:

  • nội tuyến: 8ms (0,65ns mỗi cuộc gọi)
  • trực tiếp: 68ms (5,53ns mỗi cuộc gọi)
  • ảo: 160ms (13ns mỗi cuộc gọi)

Vì vậy, trong trường hợp này (nơi mọi thứ khớp với bộ đệm), các cuộc gọi chức năng ảo chậm hơn khoảng 20 lần so với các cuộc gọi nội tuyến. Nhưng điều này thực sự có nghĩa gì? Mỗi chuyến đi qua vòng lặp gây ra 3 * 4 * 1024 = 12,288các cuộc gọi hàm chính xác (1024 vectơ nhân với bốn thành phần nhân ba lần gọi mỗi lần thêm), vì vậy những lần này biểu thị 1000 * 12,288 = 12,288,000các lệnh gọi hàm. Vòng lặp ảo mất nhiều thời gian hơn 92ms so với vòng lặp trực tiếp, do đó, chi phí bổ sung cho mỗi cuộc gọi là 7 nano giây cho mỗi chức năng.

Từ điều này tôi kết luận: , các hàm ảo chậm hơn nhiều so với các hàm trực tiếp và không , trừ khi bạn dự định gọi chúng mười triệu lần mỗi giây, điều đó không thành vấn đề.

Xem thêm: so sánh lắp ráp được tạo ra.


Nhưng nếu chúng được gọi nhiều lần, chúng thường có thể rẻ hơn so với khi chỉ được gọi một lần. Xem blog không liên quan của tôi: phresnel.org/blog , các bài đăng có tiêu đề "Các chức năng ảo được coi là không có hại", nhưng tất nhiên nó phụ thuộc vào mức độ phức tạp của mật mã của bạn
Sebastian Mach

21
Thử nghiệm của tôi đo một tập hợp nhỏ các hàm ảo được gọi liên tục. Bài đăng trên blog của bạn giả định rằng chi phí thời gian của mã có thể được đo bằng cách đếm các hoạt động, nhưng điều đó không phải lúc nào cũng đúng; chi phí chính của một vfunc trên các bộ xử lý hiện đại là bong bóng đường ống gây ra bởi một dự đoán sai chi nhánh.
Crashworks

10
đây sẽ là một điểm chuẩn tuyệt vời cho gcc LTO (Tối ưu hóa thời gian liên kết); hãy thử biên dịch lại cái này với lto enable: gcc.gnu.org/wiki/LinkTimeOptimization và xem điều gì xảy ra với yếu tố 20x
lurscher

1
Nếu một lớp có một hàm ảo và một hàm nội tuyến, hiệu năng của phương thức không ảo cũng sẽ bị ảnh hưởng? Đơn giản bởi bản chất của lớp là ảo?
thomthom

4
@thomthom Không, ảo / không ảo là thuộc tính theo chức năng. Một hàm chỉ cần được xác định thông qua vtable nếu nó được đánh dấu là ảo hoặc nếu nó ghi đè một lớp cơ sở có nó là ảo. Bạn sẽ thường thấy các lớp có một nhóm các chức năng ảo cho giao diện công cộng, và sau đó rất nhiều người truy cập nội tuyến, v.v. (Về mặt kỹ thuật, đây là thực hiện cụ thể và một trình biên dịch có thể sử dụng ponters ảo ngay cả đối với các chức năng đánh dấu 'inline', nhưng một người đã viết một trình biên dịch như vậy sẽ phát điên.)
Crashworks

42

Khi Objective-C (trong đó tất cả các phương thức là ảo) là ngôn ngữ chính cho iPhone và freakin ' Java là ngôn ngữ chính cho Android, tôi nghĩ sẽ khá an toàn khi sử dụng các chức năng ảo C ++ trên các tháp lõi kép 3 GHz của chúng tôi.


4
Tôi không chắc chắn iPhone là một ví dụ điển hình về mã hiệu suất: youtube.com/watch?v=Pdk2cJpSXLg
Crashworks

13
@Crashworks: iPhone hoàn toàn không phải là một ví dụ về mã. Đó là một ví dụ về phần cứng - cụ thể là phần cứng chậm , đó là điểm tôi đang thực hiện ở đây. Nếu những ngôn ngữ "chậm" này là đủ tốt cho phần cứng yếu, các chức năng ảo sẽ không phải là một vấn đề lớn.
Chuck

52
IPhone chạy trên bộ xử lý ARM. Bộ xử lý ARM được sử dụng cho iOS được thiết kế để sử dụng ít MHz và năng lượng thấp. Không có silicon cho dự đoán nhánh trên CPU và do đó, không có chi phí hiệu năng từ dự đoán nhánh bị bỏ lỡ từ các cuộc gọi chức năng ảo. Ngoài ra, phần cứng MHz cho iOS đủ thấp để bộ nhớ cache không làm chậm bộ xử lý trong 300 chu kỳ xung nhịp trong khi nó lấy dữ liệu từ RAM. Lỗi bộ nhớ cache ít quan trọng hơn ở tần số thấp hơn. Nói tóm lại, không có chi phí sử dụng các chức năng ảo trên thiết bị iOS, nhưng đây là vấn đề phần cứng và không áp dụng cho CPU máy tính để bàn.
HaltingState

4
Là một lập trình viên Java lâu năm mới vào C ++, tôi muốn thêm trình biên dịch JIT và trình tối ưu hóa thời gian chạy của Java có khả năng biên dịch, dự đoán và thậm chí nội tuyến một số hàm trong thời gian chạy sau một số vòng lặp được xác định trước. Tuy nhiên tôi không chắc chắn nếu C ++ có tính năng như vậy tại thời gian biên dịch và liên kết vì nó thiếu mẫu cuộc gọi thời gian chạy. Do đó, trong C ++, chúng ta có thể cần phải cẩn thận hơn một chút.
Alex Suo

@AlexSuo Tôi không chắc về quan điểm của bạn? Được biên dịch, tất nhiên C ++ không thể tối ưu hóa dựa trên những gì có thể xảy ra trong thời gian chạy, do đó, việc dự đoán sẽ phải được thực hiện bởi chính CPU ... nhưng trình biên dịch C ++ tốt (nếu được hướng dẫn) sẽ có thời lượng dài để tối ưu hóa các chức năng và vòng lặp từ lâu thời gian chạy.
underscore_d

34

Trong các ứng dụng quan trọng rất hiệu năng (như trò chơi video), một cuộc gọi chức năng ảo có thể quá chậm. Với phần cứng hiện đại, mối quan tâm hiệu năng lớn nhất là lỗi bộ nhớ cache. Nếu dữ liệu không có trong bộ đệm, nó có thể là hàng trăm chu kỳ trước khi có sẵn.

Một cuộc gọi chức năng bình thường có thể tạo ra lỗi bộ đệm lệnh khi CPU tìm nạp lệnh đầu tiên của chức năng mới và nó không có trong bộ đệm.

Một cuộc gọi hàm ảo trước tiên cần tải con trỏ vtable từ đối tượng. Điều này có thể dẫn đến việc bỏ lỡ bộ đệm dữ liệu. Sau đó, nó tải con trỏ hàm từ vtable, điều này có thể dẫn đến việc bỏ lỡ bộ đệm dữ liệu khác. Sau đó, nó gọi hàm có thể dẫn đến lỗi bộ đệm lệnh như hàm không ảo.

Trong nhiều trường hợp, hai lỗi bộ nhớ cache bổ sung không phải là một mối quan tâm, nhưng trong một vòng lặp chặt chẽ về mã quan trọng về hiệu năng, nó có thể làm giảm đáng kể hiệu năng.


6
Đúng, nhưng bất kỳ mã nào (hoặc vtable) được gọi lặp đi lặp lại từ một vòng lặp chặt chẽ (tất nhiên) sẽ hiếm khi bị lỗi bộ nhớ cache. Ngoài ra, con trỏ vtable thường nằm trong cùng một dòng bộ đệm với các dữ liệu khác trong đối tượng mà phương thức được gọi sẽ truy cập, vì vậy chúng ta thường chỉ nói về một lỗi bộ nhớ cache bổ sung.
Qwertie

5
@Qwertie Tôi không nghĩ điều đó là đúng. Phần thân của vòng lặp (nếu lớn hơn bộ đệm L1) có thể "rút" con trỏ vtable, con trỏ hàm và lần lặp tiếp theo sẽ phải chờ truy cập bộ đệm L2 (hoặc nhiều hơn) trên mỗi lần lặp
Ghita

30

Từ trang 44 của hướng dẫn "Tối ưu hóa phần mềm trong C ++" của Agner Fog :

Thời gian cần để gọi một hàm thành viên ảo là một vài chu kỳ nhiều hơn so với việc gọi một hàm thành viên không ảo, với điều kiện là câu lệnh gọi hàm luôn gọi cùng một phiên bản của hàm ảo. Nếu phiên bản thay đổi thì bạn sẽ bị phạt sai từ 10 - 30 chu kỳ đồng hồ. Các quy tắc dự đoán và đánh giá sai các cuộc gọi chức năng ảo cũng giống như đối với các câu lệnh chuyển đổi ...


Cảm ơn đã tham khảo. Hướng dẫn tối ưu hóa của Agner Fog là tiêu chuẩn vàng để sử dụng tối ưu phần cứng.
Arto Bendiken

Dựa trên hồi ức của tôi và tìm kiếm nhanh - stackoverflow.com/questions/17061967/c-switch-and-jump-tables - Tôi nghi ngờ điều này luôn đúng với switch. Với các casegiá trị hoàn toàn tùy ý , chắc chắn. Nhưng nếu tất cả caseđều liên tiếp, một trình biên dịch có thể tối ưu hóa điều này thành một bảng nhảy (ah, điều đó nhắc nhở tôi về những ngày Z80 cũ tốt), đó là (vì muốn có một thuật ngữ tốt hơn) liên tục. Không phải là tôi khuyên bạn nên cố gắng thay thế vfuncs switch, đó là lố bịch. ;)
underscore_d

7

chắc chắn rồi. Đó là một vấn đề trở lại khi máy tính chạy ở tốc độ 100Mhz, vì mọi cuộc gọi phương thức đều yêu cầu tra cứu trên vtable trước khi nó được gọi. Nhưng hôm nay .. trên CPU 3Ghz có bộ đệm cấp 1 có nhiều bộ nhớ hơn máy tính đầu tiên của tôi không? Không có gì. Phân bổ bộ nhớ từ RAM chính sẽ khiến bạn tốn nhiều thời gian hơn nếu tất cả các chức năng của bạn là ảo.

Giống như ngày xưa, ngày xưa, nơi mọi người nói lập trình có cấu trúc chậm vì tất cả các mã được chia thành các hàm, mỗi hàm yêu cầu phân bổ ngăn xếp và gọi hàm!

Lần duy nhất tôi thậm chí nghĩ đến việc bận tâm xem xét tác động hiệu năng của một chức năng ảo, là nếu nó được sử dụng rất nhiều và được khởi tạo trong mã templated kết thúc trong tất cả mọi thứ. Thậm chí sau đó, tôi sẽ không dành quá nhiều nỗ lực cho nó!

PS nghĩ về các ngôn ngữ 'dễ sử dụng' khác - tất cả các phương thức của chúng là ảo dưới vỏ bọc và ngày nay chúng không thu thập dữ liệu.


4
Chà, ngay cả ngày nay tránh các cuộc gọi chức năng là rất quan trọng đối với các ứng dụng hoàn hảo. Sự khác biệt là, các trình biên dịch ngày nay đáng tin cậy các hàm nhỏ nội tuyến nên chúng ta không phải chịu các hình phạt tốc độ khi viết các hàm nhỏ. Đối với các chức năng ảo, CPU thông minh có thể dự đoán nhánh thông minh trên chúng. Thực tế là các máy tính cũ chậm hơn là, tôi nghĩ, không thực sự là vấn đề - vâng, chúng chậm hơn nhiều, nhưng sau đó chúng tôi biết điều đó, vì vậy chúng tôi đã cho chúng khối lượng công việc nhỏ hơn nhiều. Vào năm 1992 nếu chúng tôi chơi MP3, chúng tôi biết rằng chúng tôi có thể phải dành hơn một nửa số CPU cho nhiệm vụ đó.
Qwertie

6
mp3 có từ năm 1995. vào năm 92, chúng tôi hầu như không có 386, không có cách nào họ có thể phát mp3 và 50% thời gian cpu đảm nhận một hệ điều hành đa tác vụ tốt, một quy trình nhàn rỗi và một bộ lập lịch ưu tiên. Không ai trong số này tồn tại trên thị trường tiêu dùng tại thời điểm đó. đó là 100% kể từ thời điểm bật điện, kết thúc câu chuyện.
v.oddou

7

Có một tiêu chí hiệu suất khác bên cạnh thời gian thực hiện. Một Vtable cũng chiếm không gian bộ nhớ và trong một số trường hợp có thể tránh được: ATL sử dụng " liên kết động mô phỏng " thời gian biên dịch với các mẫuđể có được hiệu ứng của "đa hình tĩnh", đó là loại khó giải thích; về cơ bản bạn chuyển lớp dẫn xuất dưới dạng tham số cho mẫu lớp cơ sở, vì vậy tại thời điểm biên dịch, lớp cơ sở "biết" lớp dẫn xuất của nó là gì trong mỗi trường hợp. Không cho phép bạn lưu trữ nhiều lớp dẫn xuất khác nhau trong một tập hợp các loại cơ sở (đó là đa hình thời gian chạy) nhưng từ ý nghĩa tĩnh, nếu bạn muốn tạo một lớp Y giống như một lớp mẫu X có từ trước móc cho kiểu ghi đè này, bạn chỉ cần ghi đè các phương thức bạn quan tâm, và sau đó bạn có được các phương thức cơ bản của lớp X mà không cần phải có vtable.

Trong các lớp có dấu chân bộ nhớ lớn, chi phí của một con trỏ vtable duy nhất không nhiều, nhưng một số lớp ATL trong COM rất nhỏ và đáng để tiết kiệm nếu trường hợp đa hình thời gian chạy không bao giờ xảy ra.

Xem thêm câu hỏi SO khác này .

Nhân tiện, đây là một bài đăng tôi thấy nói về các khía cạnh hiệu suất thời gian của CPU.


1
Nó được gọi là đa hình tham số
tjysdsg

4

Vâng, bạn đã đúng và nếu bạn tò mò về chi phí của chức năng ảo, bạn có thể thấy bài đăng này thú vị.


1
Bài viết được liên kết không xem xét một phần rất quan trọng của cuộc gọi ảo và đó có thể là sự hiểu sai về chi nhánh.
Suma

4

Cách duy nhất mà tôi có thể thấy rằng một hàm ảo sẽ trở thành một vấn đề về hiệu năng là nếu nhiều hàm ảo được gọi trong một vòng lặp chặt chẽ, và nếu và chỉ khi chúng gây ra lỗi trang hoặc hoạt động bộ nhớ "nặng" khác xảy ra.

Mặc dù như những người khác đã nói rằng nó gần như không bao giờ là vấn đề đối với bạn trong cuộc sống thực. Và nếu bạn nghĩ đó là, hãy chạy một trình hồ sơ, thực hiện một số thử nghiệm và xác minh xem đây có thực sự là vấn đề hay không trước khi thử "hủy thiết kế" mã của bạn để mang lại lợi ích hiệu suất.


2
gọi bất cứ thứ gì trong một vòng lặp chặt chẽ có khả năng giữ cho tất cả mã và dữ liệu đó được nóng trong bộ nhớ cache ...
Greg Rogers

2
Có, nhưng nếu vòng lặp bên phải đang lặp qua một danh sách các đối tượng thì mỗi đối tượng có khả năng có thể gọi một hàm ảo tại một địa chỉ khác nhau thông qua cùng một lệnh gọi hàm.
Daemin

3

Khi phương thức lớp không ảo, trình biên dịch thường thực hiện in-liner. Ngược lại, khi bạn sử dụng con trỏ tới một số lớp có chức năng ảo, địa chỉ thực sẽ chỉ được biết khi chạy.

Điều này được minh họa rõ bằng thử nghiệm, chênh lệch thời gian ~ 700% (!):

#include <time.h>

class Direct
{
public:
    int Perform(int &ia) { return ++ia; }
};

class AbstrBase
{
public:
    virtual int Perform(int &ia)=0;
};

class Derived: public AbstrBase
{
public:
    virtual int Perform(int &ia) { return ++ia; }
};


int main(int argc, char* argv[])
{
    Direct *pdir, dir;
    pdir = &dir;

    int ia=0;
    double start = clock();
    while( pdir->Perform(ia) );
    double end = clock();
    printf( "Direct %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );

    Derived drv;
    AbstrBase *ab = &drv;

    ia=0;
    start = clock();
    while( ab->Perform(ia) );
    end = clock();
    printf( "Virtual: %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );

    return 0;
}

Tác động của cuộc gọi chức năng ảo phụ thuộc nhiều vào tình huống. Nếu có ít cuộc gọi và khối lượng công việc đáng kể bên trong chức năng - nó có thể không đáng kể.

Hoặc, khi đó là một cuộc gọi ảo được sử dụng nhiều lần, trong khi thực hiện một số thao tác đơn giản - nó có thể thực sự lớn.


4
Một cuộc gọi chức năng ảo là đắt tiền so với ++ia. Vậy thì sao?
Bo Persson

2

Tôi đã qua lại về điều này ít nhất 20 lần trong dự án cụ thể của mình. Mặc dù có thể có một số lợi ích lớn về việc sử dụng lại mã, sự rõ ràng, khả năng bảo trì và khả năng đọc, mặt khác, các lần truy cập hiệu năng vẫn làm được tồn tại với các chức năng ảo.

Có phải hiệu suất đạt được sẽ được chú ý trên một máy tính xách tay / máy tính để bàn / máy tính bảng hiện đại ... có lẽ là không! Tuy nhiên, trong một số trường hợp nhất định với các hệ thống nhúng, lần truy cập hiệu năng có thể là yếu tố thúc đẩy sự kém hiệu quả của mã của bạn, đặc biệt nếu chức năng ảo được gọi đi gọi lại nhiều lần trong một vòng lặp.

Đây là một bài báo ngày nào đó thực hiện các cách thực hành tốt nhất cho C / C ++ trong ngữ cảnh hệ thống nhúng: http://www.open-std.org/jtc1/sc22/wg21/docs/ESC_Boston_01_304_apers.pdf

Để kết luận: tùy thuộc vào lập trình viên để hiểu những ưu / nhược điểm của việc sử dụng một cấu trúc nhất định so với cấu trúc khác. Trừ khi bạn siêu hiệu suất, bạn có thể không quan tâm đến hiệu suất và nên sử dụng tất cả các công cụ OO gọn gàng trong C ++ để giúp mã của bạn có thể sử dụng được càng tốt.


2

Theo kinh nghiệm của tôi, điều liên quan chính là khả năng nội tuyến một chức năng. Nếu bạn có nhu cầu về hiệu năng / tối ưu hóa để ra lệnh cho một chức năng cần được nội tuyến, thì bạn không thể biến chức năng này thành ảo vì điều đó sẽ ngăn chặn điều đó. Nếu không, bạn có thể sẽ không nhận thấy sự khác biệt.


1

Một điều cần lưu ý là:

boolean contains(A element) {
    for (A current: this)
        if (element.equals(current))
            return true;
    return false;
}

có thể nhanh hơn thế này:

boolean contains(A element) {
    for (A current: this)
        if (current.equals(equals))
            return true;
    return false;
}

Điều này là do phương thức đầu tiên chỉ gọi một hàm trong khi phương thức thứ hai có thể gọi nhiều hàm khác nhau. Điều này áp dụng cho bất kỳ chức năng ảo trong bất kỳ ngôn ngữ.

Tôi nói "có thể" bởi vì điều này phụ thuộc vào trình biên dịch, bộ đệm, v.v.


0

Hình phạt hiệu năng của việc sử dụng các chức năng ảo không bao giờ có thể vượt quá những lợi thế bạn có được ở cấp thiết kế. Giả sử một cuộc gọi đến một chức năng ảo sẽ kém hiệu quả hơn 25% sau đó là một cuộc gọi trực tiếp đến một chức năng tĩnh. Điều này là do có một mức độ gián tiếp thông qua VMT. Tuy nhiên, thời gian thực hiện cuộc gọi thường rất nhỏ so với thời gian thực hiện chức năng thực tế của bạn, do đó tổng chi phí hiệu năng sẽ không thể kiểm soát được, đặc biệt là với hiệu suất phần cứng hiện tại. Hơn nữa, trình biên dịch đôi khi có thể tối ưu hóa và thấy rằng không cần cuộc gọi ảo nào và biên dịch nó thành một cuộc gọi tĩnh. Vì vậy, đừng lo lắng sử dụng các hàm ảo và các lớp trừu tượng nhiều như bạn cần.


2
không bao giờ, cho dù máy tính mục tiêu nhỏ như thế nào?
bảo vệ zumalififard

Tôi hẳn đã đồng ý có bạn phrased rằng như The performance penalty of using virtual functions can sometimes be so insignificant that it is completely outweighed by the advantages you get at the design level.Sự khác biệt chính là nói sometimes, không never.
gạch dưới

-1

Tôi luôn tự đặt câu hỏi cho mình về điều này, đặc biệt là từ - cách đây vài năm - tôi cũng đã làm một bài kiểm tra như vậy so sánh thời gian của một cuộc gọi phương thức thành viên tiêu chuẩn với một cuộc gọi ảo và thực sự tức giận về kết quả lúc đó, có những cuộc gọi ảo trống rỗng Chậm hơn 8 lần so với phi ảo.

Hôm nay tôi phải quyết định có sử dụng chức năng ảo để phân bổ thêm bộ nhớ trong lớp đệm của mình hay không, trong một ứng dụng quan trọng về hiệu năng, vì vậy tôi đã tìm hiểu (và tìm thấy bạn), và cuối cùng, đã làm lại bài kiểm tra.

// g++ -std=c++0x -o perf perf.cpp -lrt
#include <typeinfo>    // typeid
#include <cstdio>      // printf
#include <cstdlib>     // atoll
#include <ctime>       // clock_gettime

struct Virtual { virtual int call() { return 42; } }; 
struct Inline { inline int call() { return 42; } }; 
struct Normal { int call(); };
int Normal::call() { return 42; }

template<typename T>
void test(unsigned long long count) {
    std::printf("Timing function calls of '%s' %llu times ...\n", typeid(T).name(), count);

    timespec t0, t1;
    clock_gettime(CLOCK_REALTIME, &t0);

    T test;
    while (count--) test.call();

    clock_gettime(CLOCK_REALTIME, &t1);
    t1.tv_sec -= t0.tv_sec;
    t1.tv_nsec = t1.tv_nsec > t0.tv_nsec
        ? t1.tv_nsec - t0.tv_nsec
        : 1000000000lu - t0.tv_nsec;

    std::printf(" -- result: %d sec %ld nsec\n", t1.tv_sec, t1.tv_nsec);
}

template<typename T, typename Ua, typename... Un>
void test(unsigned long long count) {
    test<T>(count);
    test<Ua, Un...>(count);
}

int main(int argc, const char* argv[]) {
    test<Inline, Normal, Virtual>(argc == 2 ? atoll(argv[1]) : 10000000000llu);
    return 0;
}

Và thực sự ngạc nhiên rằng nó - thực tế - thực sự không còn quan trọng nữa. Mặc dù nó có ý nghĩa để có các dòng nội tuyến nhanh hơn so với không phải ảo và chúng nhanh hơn ảo, nhưng nó thường liên quan đến tải của máy tính, cho dù bộ nhớ cache của bạn có dữ liệu cần thiết hay không, và trong khi bạn có thể tối ưu hóa ở cấp độ bộ đệm, tôi nghĩ rằng điều này nên được thực hiện bởi các nhà phát triển trình biên dịch hơn là bởi các nhà phát triển ứng dụng.


12
Tôi nghĩ rằng rất có khả năng trình biên dịch của bạn có thể nói rằng lệnh gọi hàm ảo trong mã của bạn chỉ có thể gọi Virtual :: call. Trong trường hợp đó, nó chỉ có thể nội tuyến nó. Cũng không có gì ngăn trình biên dịch thực hiện cuộc gọi Bình thường :: mặc dù bạn không yêu cầu trình biên dịch. Vì vậy, tôi nghĩ rằng hoàn toàn có thể bạn nhận được cùng một lúc cho 3 thao tác vì trình biên dịch đang tạo mã giống hệt nhau cho chúng.
Bjarke H. Roune
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.