Тест на издержки виртуальных функций - PullRequest
2 голосов
/ 29 июня 2010

Я создал (возможно, очень ненаучный) небольшой тест, чтобы определить издержки виртуальных функций в одноуровневом одиночном наследовании, и результаты, которые я получил, были, в общем-то, одинаковыми при доступе к производному классу полиморфно или при доступе к немунепосредственно.Что было немного удивительно, так это порядок времени вычислений, который вводится, когда любая функция объявляется виртуальной (см. Результаты ниже).

Слишком много служебной информации при объявлении функций-членов как таковых, и почему они все еще присутствуют даже при непосредственном доступе к производному классу?

Код выглядит следующим образом:

class base
{
public:
    virtual ~base() {}
    virtual uint func(uint i) = 0;
};

class derived : public base
{
public:
    ~derived() {}
    uint func(uint i) { return i * 2; }
};

uint j = 0;
ulong k = 0;
double l = 0;
ushort numIters = 10;
base* mybase = new derived;  // or derived* myderived = ...

for(ushort i = 0; i < numIters; i++)
{
  clock_t start2, finish2;
  start2 = clock();

  for (uint j = 0; j < 100000000; ++j)
        k += mybase->func(j);

  finish2 = clock();
  l += (double) (finish2 - start2);
  std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl;

}

std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl;
std::cout << "Average duration: " << l / numIters << " ms." << std::endl;

Результаты:

base* mybase = new derived; дает в среднем ~ 338 мс.

derived* myderived = new derived; дает в среднем ~ 338 мс.

Устранение наследования и удаление виртуальногофункции дают в среднем ~ 38 мс.

Это почти в 10 раз меньше!Таким образом, в принципе, если какая-либо функция объявлена ​​виртуальной, накладные расходы всегда будут присутствовать одинаково, даже если я не использую ее полиморфно?

Спасибо.

Ответы [ 2 ]

6 голосов
/ 29 июня 2010

Доступ к нему «напрямую» выполняет ту же работу, что и доступ к нему «косвенно».

Когда вы вызываете функцию на myderived, сохраненный там указатель может указывать на некоторый объект некоторого класса, производного от derived. Компилятор не может предположить, что это действительно объект derived, это может быть объект другого производного класса, который переопределяет виртуальную функцию, поэтому должна выполняться диспетчеризация виртуальной функции, как в случае mybase. В обоих случаях функция ищется в таблице виртуальных функций перед ее вызовом.

Чтобы вызвать функцию не полиморфно, не используйте указатель:

derived myderived;
myderived.func(1); 

Когда вы удаляете виртуальные функции, компилятор может встроить вызов функции так, что вы в итоге получите простой цикл:

for (uint j = 0; j < 100000000; ++j)
    k += i * 2;

Это намного быстрее, поскольку вы экономите накладные расходы на вызовы функций 100000000, и компилятор может даже оптимизировать цикл дальше, чем если бы в нем был вызов функции.

Обратите внимание, что разница между встроенной версией и вызовом виртуальной функции была бы намного меньше, если бы функция выполняла какую-то реальную работу. В этом примере тело функции почти не занимает времени, поэтому затраты на вызов функции перевешивают затраты на выполнение тела.

2 голосов
/ 29 июня 2010

Виртуальные функции практически ничего не стоят. Большинство реальных проблем с производительностью вызваны излишне пушистыми деревьями вызовов, делающими вещи, о которых вы и не подозревали.

То, как я их нахожу, заключается в том, что несколько раз приостанавливаю приложение под отладчиком и проверяю состояние, включая стек вызовов. Вот пример использования этого метода для ускорения в 43 раза.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...