Виртуальные функции и производительность C ++ - PullRequest
7 голосов
/ 02 февраля 2011

Прежде чем вы съеживаете дублирующее название, другой вопрос не соответствовал тому, что я задаю здесь (IMO).Итак.

Я действительно хочу использовать виртуальные функции в своем приложении, чтобы упростить процесс в сто раз (разве не в этом смысл ООП;)).Но я где-то читал, что они стоили производительности, не видя ничего, кроме той же затейливой шумихи о преждевременной оптимизации, и я решил сделать это в небольшом тесте, используя:

CProfiler.cpp

#include "CProfiler.h"

CProfiler::CProfiler(void (*func)(void), unsigned int iterations) {
    gettimeofday(&a, 0);
    for (;iterations > 0; iterations --) {
        func();
    }
    gettimeofday(&b, 0);
    result = (b.tv_sec * (unsigned int)1e6 + b.tv_usec) - (a.tv_sec * (unsigned int)1e6 + a.tv_usec);
};

main.cpp

#include "CProfiler.h"

#include <iostream>

class CC {
  protected:
    int width, height, area;
  };

class VCC {
  protected:
    int width, height, area;
  public:
    virtual void set_area () {}
  };

class CS: public CC {
  public:
    void set_area () { area = width * height; }
  };

class VCS: public VCC {
  public:
    void set_area () {  area = width * height; }
  };

void profileNonVirtual() {
    CS *abc = new CS;
    abc->set_area();
    delete abc;
}

void profileVirtual() {
    VCS *abc = new VCS;
    abc->set_area();
    delete abc;
}

int main() {
    int iterations = 5000;
    CProfiler prf2(&profileNonVirtual, iterations);
    CProfiler prf(&profileVirtual, iterations);

    std::cout << prf.result;
    std::cout << "\n";
    std::cout << prf2.result;

    return 0;
}

Сначала я выполнял только 100 и 10000 итераций, и результаты вызывали беспокойство: 4 мс для не виртуализированныхи 250мс для виртуализированных!Я чуть было не «ооооооо» внутри, но затем я увеличил количество итераций примерно до 500 000;чтобы результаты стали почти полностью идентичными (возможно, на 5% медленнее без включенных флагов оптимизации).

Мой вопрос: почему произошло такое значительное изменение с небольшим количеством итераций по сравнению с большим количеством?Было ли это только потому, что виртуальные функции были горячими в кеше на этих многих итерациях?

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

Ответы [ 8 ]

11 голосов
/ 02 февраля 2011

Я считаю, что ваш тестовый пример слишком искусственный, чтобы иметь какую-либо большую ценность.

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

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

5 голосов
/ 02 февраля 2011

Расширение Ответ Чарльза .

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

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

template <typename Type>
double benchmark(Type const& t, size_t iterations)
{
  timeval a, b;
  gettimeofday(&a, 0);
  for (;iterations > 0; --iterations) {
    t.getArea();
  }
  gettimeofday(&b, 0);
  return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) -
         (a.tv_sec * (unsigned int)1e6 + a.tv_usec);
}

Классы:

struct Regular
{
  Regular(size_t w, size_t h): _width(w), _height(h) {}

  size_t getArea() const;

  size_t _width;
  size_t _height;
};

// The following line in another translation unit
// to avoid inlining
size_t Regular::getArea() const { return _width * _height; }

struct Base
{
  Base(size_t w, size_t h): _width(w), _height(h) {}

  virtual size_t getArea() const = 0;

  size_t _width;
  size_t _height;
};

struct Derived: Base
{
  Derived(size_t w, size_t h): Base(w, h) {}

  virtual size_t getArea() const;
};

// The following two functions in another translation unit
// to avoid inlining
size_t Derived::getArea() const  { return _width * _height; }

std::auto_ptr<Base> generateDerived()
{
  return std::auto_ptr<Base>(new Derived(3,7));
}

И измерения:

int main(int argc, char* argv[])
{
  if (argc != 2) {
    std::cerr << "Usage: %prog iterations\n";
    return 1;
  }

  Regular regular(3, 7);
  std::auto_ptr<Base> derived = generateDerived();

  double regTime = benchmark<Regular>(regular, atoi(argv[1]));
  double derTime = benchmark<Base>(*derived, atoi(argv[1]));

  std::cout << "Regular: " << regTime << "\nDerived: " << derTime << "\n";

  return 0;
}

Примечание: это проверяет издержки виртуального вызова по сравнению с обычной функцией. Функциональные возможности различны (поскольку во втором случае у вас нет диспетчеризации во время выполнения), но это накладные расходы в худшем случае.

EDIT

Результаты выполнения (gcc.3.4.2, -O2, четырехъядерный сервер SLES10) примечание: с определениями функций в другом модуле перевода, для предотвращения встраивания

> ./test 5000000
Regular: 17041
Derived: 17194

Не совсем убедительно.

3 голосов
/ 02 февраля 2011

При небольшом числе итераций существует вероятность того, что ваш код будет вытеснен какой-либо другой программой, выполняющейся параллельно, или произойдет свопинг, или что-либо еще операционная система изолирует вашу программу от выполнения, и у вас будет время, когда она была приостановлена ​​операционнойСистема включена в ваши результаты теста.Это причина номер один, почему вы должны выполнить свой код около десятка миллионов раз, чтобы измерить что-либо более или менее надежно.

2 голосов
/ 02 февраля 2011

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

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

2 голосов
/ 02 февраля 2011

Может быть несколько причин для разницы во времени.

  • Ваша функция синхронизации не достаточно точна
  • менеджер кучи может повлиять на результат, потому что sizeof(VCS) > sizeof(VS). Что произойдет, если вы переместите new / delete из цикла?

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

НО: вам действительно нужно сравнить похожую функциональность. При использовании виртуальных функций вы делаете это по причине, которая вызывает другую функцию-член, зависящую от идентичности объекта. Если вам нужна эта функциональность и вы не хотите использовать виртуальные функции, вам придется реализовать ее вручную, будь то с помощью таблицы функций или даже оператора switch. Это тоже дорого, и это то, что вы должны сравнить с виртуальными функциями.

2 голосов
/ 02 февраля 2011

Я думаю, что этот вид тестирования довольно бесполезен, на самом деле:
1) вы тратите время на профилирование, вызывая gettimeofday();
2) вы на самом деле не тестируете виртуальные функции, и имхо это худшая вещь.

Почему? Потому что вы используете виртуальные функции, чтобы избежать написания таких вещей, как:

<pseudocode>
switch typeof(object) {

case ClassA: functionA(object);

case ClassB: functionB(object);

case ClassC: functionC(object);
}
</pseudocode>

в этом коде вы пропускаете блок «если ... еще», поэтому вы не пользуетесь виртуальными функциями. Это сценарий, в котором они всегда «проигрывают» против не-виртуальных.

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

1 голос
/ 02 февраля 2011

При использовании слишком небольшого количества итераций при измерении возникает много шума.Функция gettimeofday не будет достаточно точной, чтобы дать вам хорошие измерения только для нескольких итераций, не говоря уже о том, что она записывает общее время стены (которое включает время, потраченное при вытеснении другими потоками).

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

0 голосов
/ 04 октября 2015

По моему мнению, когда было меньше циклов, возможно, не было переключения контекста, но когда вы увеличили количество циклов, очень высоки шансы, что переключение контекста имеет место и доминирует в чтении.Например, первая программа занимает 1 секунду, а вторая - 3 секунды, но если переключение контекста занимает 10 секунд, то разница составляет 13/11 вместо 3 / 1.

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