Примитивные типы медленнее, чем пользовательские типы в С++?

Мне стало любопытно, и я провел небольшой тест, чтобы определить разницу в производительности между примитивными типами, такими как int или float, и пользовательскими типами.

Я создал класс шаблона Var, создал несколько встроенных арифметических операторов. Тест состоял из зацикливания этого цикла как для примитивного, так и для Var векторов:

for (unsigned i = 0; i < 1000; ++i) {
    in1[i] = i;
    in2[i] = -i;
    out[i] = (i % 2) ? in1[i] + in2[i] : in2[i] - in1[i];
}

Я был очень удивлен результатами, оказалось, что мой класс Var большую часть времени работает быстрее, с int в среднем этот цикл занимает примерно на 5700 нс меньше с классом. Из 3000 запусков int оказался быстрее в 11 раз, а Var — в 2989 раз. Аналогичные результаты с float, где Var на 15100 нс быстрее, чем floatin 2991 прогонов.

Разве примитивные типы не должны быть быстрее?

Редактировать: компилятор - это довольно древний mingw 4.4.0, параметры сборки - значения по умолчанию для QtCreator, без оптимизации:

qmake call: qmake.exe C:\...\untitled15.pro -r -spec win32-g++ "CONFIG+=release"

ОК, публикую полный исходный код, платформа 64-битная Win7, 4 ГБ DDR2-800, Core2Duo@3Ghz

#include <QTextStream>
#include <QVector>
#include <QElapsedTimer>

template<typename T>
class Var{
public:
    Var() {}
    Var(T val) : var(val) {}

    inline T operator+(Var& other)
    {
        return var + other.value();
    }

    inline T operator-(Var& other)
    {
        return var - other.value();
    }

    inline T operator+(T& other)
    {
        return var + other;
    }

    inline T operator-(T& other)
    {
        return var - other;
    }

    inline void operator=(T& other)
    {
        var = other;
    }

    inline T& value()
    {
        return var;
    }

private:
    T var;
};

int main()
{
    QTextStream cout(stdout);
    QElapsedTimer timer;

    unsigned count = 1000000;

    QVector<double> pin1(count), pin2(count), pout(count);
    QVector<Var<double> > vin1(count), vin2(count), vout(count);

    unsigned t1, t2, pAcc = 0, vAcc = 0, repeat = 10, pcount = 0, vcount = 0, ecount = 0;
    for (int cc = 0; cc < 5; ++cc)
    {
        for (unsigned c = 0; c < repeat; ++c)
        {
            timer.restart();
            for (unsigned i = 0; i < count; ++i)
            {
                pin1[i] = i;
                pin2[i] = -i;
                pout[i] = (i % 2) ? pin1[i] + pin2[i] : pin2[i] - pin1[i];
            }
            t1 = timer.nsecsElapsed();
            cout << t1 << endl;

            timer.restart();
            for (unsigned i = 0; i < count; ++i)
            {
                vin1[i] = i;
                vin2[i] = -i;
                vout[i] = (i % 2) ? vin1[i] + vin2[i] : vin2[i] - vin1[i];
            }
            t2 = timer.nsecsElapsed();
            cout << t2 << endl;;
            pAcc += t1;
            vAcc += t2;
        }

        pAcc /= repeat;
        vAcc /= repeat;
        if (pAcc < vAcc) {
            cout << "primitive was faster" << endl;
            pcount++;
        }
        else if (pAcc > vAcc) {
            cout << "var was faster" << endl;
            vcount++;
        }
        else {
            cout << "amazingly, both are equally fast" << endl;
            ecount++;
        }

        cout << "Average for primitive type is " << pAcc << ", average for Var is " << vAcc << endl;

    }
    cout << "int was faster " << pcount << " times, var was faster " << vcount << " times, equal " << ecount << " times, " << pcount + vcount + ecount << " times ran total" << endl;
}

Относительно, с числами с плавающей запятой класс Var на 6-7% быстрее, чем с числами с плавающей запятой, с целыми примерно на 3%.

Я также провел тест с длиной вектора 10 000 000 вместо исходных 1000, и результаты по-прежнему согласуются и в пользу класса.


person dtech    schedule 06.06.2012    source источник
comment
Пожалуйста, добавьте компилятор и параметры сборки. И реализация Var тоже.   -  person Forgottn    schedule 06.06.2012
comment
и сравните ассемблерный код.   -  person RolandXu    schedule 06.06.2012
comment
В зависимости от того, как вы измеряете, этот цикл может выполняться (намного) слишком короткое время, чтобы получить точные или воспроизводимые данные о производительности.   -  person Peter G.    schedule 06.06.2012
comment
Кроме того, 5700нс или 15100нс из скольки всего? Относительное сравнение отсутствует. На какой платформе вы измеряете? Ака @Forgottn вопрос компилятора. Какой метод измерения времени вы используете на этой платформе?   -  person Pavel Zdenek    schedule 06.06.2012
comment
Тестировали ли вы сначала версию int, а затем версию класса в рамках одного прогона? Это могло абсолютно исказить результаты, если что-то было кэшировано.   -  person Mark B    schedule 06.06.2012
comment
@Питер Г. - тест выполняется 3000 раз примерно за 30 секунд, этого должно быть достаточно, чтобы свести к минимуму погрешность.   -  person dtech    schedule 06.06.2012
comment
@pmr - что именно вы имеете в виду? Оператор + для Var‹int› преобразуется в int + int?   -  person dtech    schedule 06.06.2012
comment
@ddriver: Это вполне возможно из-за рекламных акций. Для float все промежуточные значения вычисляются путем преобразования их в double. int, однако, является типом данных для всех целочисленных типов, так что это оптимально (по сравнению с любым из char, long и т. д.). Попробуйте с double и сообщите нам о результатах.   -  person dirkgently    schedule 06.06.2012
comment
@dirkgently - только что протестировано с вектором из 10 000 000 двойников, Var‹double› в среднем занимает 191895368 нсек. double занимает в среднем 207335291 нсек при 10 прогонах, что примерно на 8% быстрее для Var.   -  person dtech    schedule 06.06.2012
comment
Вы рассчитываете время объявления массива? Ваша версия класса обнуляет каждое значение при построении, в отличие от типов POD. Это вытягивает память в кеш, поэтому последующие циклы будут быстрее. Попробуйте установить для массивов POD значение 0 вне временного цикла для эквивалентного тестового примера.   -  person Peter    schedule 06.06.2012
comment
@ddriver: Хорошо, пришло время увидеть источник! (Кроме того, информация о компиляторе/платформе, как уже отмечали другие.)   -  person dirkgently    schedule 06.06.2012
comment
Какие типы массивов in1 и in2? Вы используете оба типа в одном цикле?   -  person victor.t    schedule 06.06.2012
comment
@Peter - удалено обнуление, добавлен полный исходный код через редактирование.   -  person dtech    schedule 06.06.2012
comment
Возможно, это проблема оптимизации, вы можете поместить объявление массивов перед основным и пометить их как volatile. А также какая разница в режиме DEBUG?   -  person victor.t    schedule 06.06.2012
comment
@victor.t - в режиме отладки примитивные типы полностью выигрывают, примерно вдвое выше производительность для double и примерно на 30% быстрее для int   -  person dtech    schedule 06.06.2012
comment
Вы пытались поместить векторы в качестве глобальных переменных и пометить их как изменчивые в режиме выпуска?   -  person victor.t    schedule 06.06.2012
comment
@victor.t - я пытался, но получаю сообщение об ошибке, когда пытаюсь изменить размер векторов в main - передавая «изменчивый QVector‹int›» в качестве «этого» аргумента «void QVector‹T›::resize(int) [ with T = int]' отбрасывает квалификаторы   -  person dtech    schedule 06.06.2012
comment
попробуйте сделать все векторы глобальными переменными, это должно помешать компилятору оптимизировать циклы   -  person victor.t    schedule 06.06.2012
comment
Зачем кому-то пытаться профилировать производительность без оптимизации?   -  person Chad    schedule 06.06.2012
comment
Потому что один из циклов может быть удален скомпилированным из-за неиспользуемого сценария.   -  person victor.t    schedule 06.06.2012
comment
@victor.t — глобальные векторы не изменили производительность, теперь проверяются необработанные массивы, чтобы исключить влияние контейнера.   -  person dtech    schedule 07.06.2012


Ответы (2)


С заменой QVector на std::vector на уровне оптимизации -O2 код, сгенерированный GCC для двух типов, будет абсолютно одинаковым, инструкция за инструкцией.

Без замены сгенерированный код отличается, но это неудивительно, учитывая, что QtVector реализуется по-разному для примитивных и непримитивных типов (ищите QTypeInfo<T>::isComplex в qvector.h).

Обновление Похоже, isComplex не влияет на linner oop, т.е. измеряемую часть. Код цикла по-прежнему различается для двух типов, хотя и очень незначительно. Похоже, что разница связана с GCC.

person n. 1.8e9-where's-my-share m.    schedule 06.06.2012
comment
Я тестировал с std::vector, а также с необработанным массивом в стиле c, со стандартными векторами производительность немного выше, чем у примитивных типов, с необработанными массивами ситуация обратная. - person dtech; 07.06.2012

Я сравнил время выполнения и выделение памяти для QVector и float* с очень небольшой разницей между ними.

person kiriloff    schedule 12.06.2014