WedX - журнал о программировании и компьютерных науках

C++ странная производительность с унаследованным классом

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

У меня есть иерархия наследования, состоящая из 3 классов: Форма, Прямоугольник, Четырехугольник. У меня есть один класс BaseQuadrilateral, который ничего не наследует и делает то же самое, что и класс Quadrilateral. В каждом классе есть два метода: surfaceArea() и volume(). Я запускаю отдельный тест для каждого класса и записываю время, необходимое для работы с 10 000 000 объектов. Я ожидал, что класс Четырехугольника займет немного больше времени. Вместо этого класс Quadrilateral (унаследованный от Rectangle) работает на порядок быстрее, чем BaseQuadrilateral. Я не понимаю, почему это так.

Test Results:
Running Dynamic Dispatch Test

Quadrilateral Runtime: 2840264 Ticks, 2 Seconds.
BaseQuadrilateral Runtime: 21179219 Ticks, 21 Seconds.

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

Спасибо

class Shape
{
public:
    virtual double surfaceArea() = 0;
    virtual double Volume() = 0;
};

class Rectangle : public Shape
{
public:
    //Constructors
    Rectangle();
    Rectangle(double, double);
    Rectangle(const Rectangle&);

    Rectangle& operator=(const Rectangle&);
    double Area();

    //Override Shape base class methods
    double surfaceArea();
    double Volume();
protected:
    double length;
    double width;
};

class Quadrilateral : public Rectangle
{
public:
    //Constructors
    Quadrilateral();
    Quadrilateral(double, double, double);
    Quadrilateral(const Quadrilateral&);

    Quadrilateral& operator=(const Quadrilateral&);

    //Overloaded Square base class
    double surfaceArea();
    double Volume();
protected:
    double height;
};

class BaseQuadrilateral
{
public:
    //Constructors
    BaseQuadrilateral();
    BaseQuadrilateral(double, double, double);
    BaseQuadrilateral(const BaseQuadrilateral&);

    BaseQuadrilateral& operator=(const BaseQuadrilateral&);
    double surfaceArea();
    double Volume();

protected:
    double length;
    double width;
    double height;
};

void test2()
{
    clock_t qTimer, bqTimer;
    Quadrilateral* quadrilaterals;
    BaseQuadrilateral* baseQuadrilaterals, baseQuadrilateral;
    Shape* shape;
    double* answers1, *answers2;
    srand((unsigned int)time(NULL));

    cout << "Running Dynamic Dispatch Test\n" << endl;

    quadrilaterals = new Quadrilateral[ARRAY_SIZE];
    baseQuadrilaterals = new BaseQuadrilateral[ARRAY_SIZE];
    answers1 = new double[ARRAY_SIZE];
    answers2 = new double[ARRAY_SIZE];

    //Initialization
    for (int i = 0; i < ARRAY_SIZE; i++)
    {
        double length = (double)(rand() % 100);
        double width = (double)(rand() % 100);
        double height = (double)(rand() % 100);

        quadrilaterals[i] = Quadrilateral(length, width, height);
        baseQuadrilaterals[i] = BaseQuadrilateral(length, width, height);
    }

    //Test Shape
    qTimer = clock();

    for (int i = 0; i < ARRAY_SIZE; i++)
    {
        shape = &quadrilaterals[i];
        answers1[i] = shape->Volume();
    }

    qTimer = clock() - qTimer;

    //Test BaseQuadrilateral
    bqTimer = clock();

    for (int i = 0; i < ARRAY_SIZE; i++)
    {
        baseQuadrilateral = baseQuadrilaterals[i];
        answers2[i] = baseQuadrilateral.Volume();
    }

    bqTimer = clock() - qTimer;

    for (int i = 0; i < ARRAY_SIZE; i++)
    {
        if (answers1[i] != answers2[i])
        {
            cout << "Incorrect answer found at i=" << i << ". answers1: " << answers1[i] << " answers2: " << answers2[i] << endl;
            break;
        }
    }

    //Print Results
    cout << "Quadrilateral Runtime: " << qTimer << " Ticks, " << qTimer / CLOCKS_PER_SEC << " Seconds." << endl;
    cout << "BaseQuadrilateral Runtime: " << bqTimer << " Ticks, " << bqTimer / CLOCKS_PER_SEC << " Seconds." << endl;
}
26.12.2015

  • Эта строка может иметь к этому отношение: bqTimer = clock() - qTimer;. Похоже, вы хотите, чтобы qTimer было bqTimer. 26.12.2015

Ответы:


1

Как я писал в комментариях, у вас есть небольшая проблема с этой строкой:

bqTimer = clock() - qTimer;

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

В первом случае вы записываете указатель на свой Quadrilateral в переменную shape, а затем косвенно вызываете метод Volume() через указатель:

for (int i = 0; i < ARRAY_SIZE; i++)
{
    shape = &quadrilaterals[i];
    answers1[i] = shape->Volume();
}

Во втором случае вы делаете копию всего объекта BaseQuadrilateral, а затем вызываете метод копии Volume():

for (int i = 0; i < ARRAY_SIZE; i++)
{
    baseQuadrilateral = baseQuadrilaterals[i];
    answers2[i] = baseQuadrilateral.Volume();
}

Копирование объекта намного дороже, чем получение его адреса. Фактически, в этом конкретном случае вычисление адреса может быть даже полностью оптимизировано. Я предлагаю избегать посредника и в обоих случаях вызывать метод непосредственно для элементов массива:

    answers1[i] = quadrilaterals[i].Volume();

or

    answers2[i] = baseQuadrilaterals[i].Volume();
26.12.2015
  • Спасибо, Джон. Я исправил проблему с таймером. Не знаю, как я пропустил это сам. Как только вы указали, что происходит копирование с baseQuadrilateral, я понял, что не объявлял это указателем, что я намеревался сделать, чтобы оба цикла были эквивалентны. Повторный запуск теста дает около 2 секунд для quadrilaterals и 3-4 секунды для baseQuadrilaterals. То же самое и с предложенными вами строками кода. Это гораздо более разумный разрыв, но я все еще не понимаю, почему baseQuadrilateral медленнее, чем quadrilateral. Почему не наоборот? 27.12.2015
  • После запуска теста еще несколько раз я иногда получаю quadrilaterals, который немного быстрее, чем baseQuadrilaterals, на несколько сотен тысяч тиков, но большую часть времени quadrilaterals происходит между 1-2 секундами, а baseQuadrilaterals - между 2-3 секундами. . 27.12.2015
  • @KelbyMadal, ваш тест теперь точно говорит вам об одном: разница в производительности между вашими двумя подходами очень мала - максимум порядка одной микросекунды на вызов для этого случая. Я могу сказать, почему измеренная производительность сравнивается не так, как вы ожидаете, но я подозреваю неэквивалентные реализации Volume() (которые вы не представили). Также может быть множество тонких различий в оптимизации или реализации теста, которые влияют на результаты. 28.12.2015
  • Спасибо за всю вашу помощь @JohnBollinger. Как вы подозреваете, это может иметь какое-то отношение к оптимизации. Реализации Volume() одинаковы для обоих классов, за исключением, конечно, той, которая помечена как виртуальная в базовом классе Shape. double Quadrilateral::Volume() { return length * width * height; } double BaseQuadrilateral::Volume() { return length * width * height; } 28.12.2015
  • Кроме того, не могли бы вы рассказать мне, как добавить разрывы строк между блоками кода. Я новичок в stackoverflow и не могу понять, как это сделать. 28.12.2015
  • @KelbyMadal Помещение HTML-комментария (без отступа) между блоками кода должно привести к тому, что они будут отформатированы как отдельные блоки, но более естественно поместить одну или две строки соответствующего текста между блоками, если на самом деле необходимо представить их как отдельные блоки. . 28.12.2015

  • 2

    Должна ли эта строка

    bqTimer = clock() - qTimer;
    

    be

    bqTimer = clock() - bqTimer;
    
    26.12.2015
    Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    Для любых предложений по сайту: [email protected]