Почему мои макросы препроцессора медленнее или быстрее, чем написанные вручную, кажутся случайными?

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

struct Bar
{
    long long a;
    long long b;
    long long c;
    long long d;
};

struct Foo
{
    Bar var[1300];
};

typedef std::vector<Foo> TEST_TYPE ;

class A
{
private:
    TEST_TYPE container;

public:
    TEST_TYPE& getcontainer()
    {
        return container;
    }
};

#define createBMember(TYPE, NAME)         \
private:                                  \
    TYPE NAME;                            \
                                          \
public:                                   \
    TYPE& get##NAME()                     \
    {                                     \
        return NAME;                      \
    }

class B
{
    createBMember(TEST_TYPE, container);
};

double testA()
{
    A a;
    LARGE_INTEGER frequency;
    LARGE_INTEGER startA, endA;

    if (!QueryPerformanceFrequency(&frequency)) {
        std::cerr << "High-Resolution-Timer nicht unterstützt." << std::endl;
        return 1;
    }

    QueryPerformanceCounter(&startA);
    for(size_t i = 0; i < 10000; ++i)
    {
        a.getcontainer().push_back(Foo());
    }

    QueryPerformanceCounter(&endA);

    return static_cast<double>(endA.QuadPart - startA.QuadPart) / frequency.QuadPart;
}

double testB()
{
    B b;
    LARGE_INTEGER frequency;
    LARGE_INTEGER startB, endB;

    if (!QueryPerformanceFrequency(&frequency)) {
        std::cerr << "High-Resolution-Timer nicht unterstützt." << std::endl;
    }

    QueryPerformanceCounter(&startB);

    for(size_t i = 0; i < 10000; ++i)
    {
        b.getcontainer().push_back(Foo());
    }

    QueryPerformanceCounter(&endB);

    return static_cast<double>(endB.QuadPart - startB.QuadPart) / frequency.QuadPart;
}

//----------------------------------------------------[main]
int main()
{
    double Atest = 0;
    double Btest = 0;

    double AHigh = 0;
    double BHigh = 0;

    double ALow = 10000;
    double BLow = 10000;

    double a;
    double b;

    const uint16_t amount = 30;

    for(uint16_t i = 0; i < amount; ++i)
    {   
        a = testA();

        AHigh = a > AHigh ? a : AHigh;
        ALow = a < ALow ? a : ALow;

        Atest += a;
    }

    for(uint8_t i = 0; i < amount; ++i)
    {   
        b = testB();

        BHigh = b > BHigh ? b : BHigh;
        BLow = b < BLow ? b : BLow;

        Btest += b;
    }

    Atest /= amount; 
    Btest /= amount; 

    std::cout << "A: " << Atest << std::endl;
    std::cout << "B: " << Btest << std::endl;

    auto size = sizeof(Foo);

    return 0;
}

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

Однако странность заключалась в том, что, хотя препроцессор запускается до компиляции и поэтому оба класса должны быть идентичными, я измерил некоторые различия в скорости. Были сделаны следующие наблюдения:

  • В режиме отладки без какой-либо оптимизации класс, который тестируется первым, работает быстрее.
  • В режиме выпуска с «оптимизацией всей программы» и другими настройками B работает быстрее. Последние разы были: A: 0,47695, B: 0,430825.

Это меня смущает, потому что, как я уже сказал, оба класса идентичны.

Я также должен упомянуть, что, к сожалению, что касается нашей среды разработки, нам приходится работать со своего рода моментальной версией C++11 (Visual Studio 2010). Вот почему я не могу использовать std::chrono, например, для сравнительного анализа.

Мне еще не удалось протестировать его с другими компиляторами. Я также просмотрел ассемблерный код на godbolt.org, но не нашел ничего, что могло бы иметь такое большое значение.

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

Вы пытались использовать один и тот же класс в обоих тестах?

molbdnilo 16.08.2024 09:13

Я еще не проверял это. Но результат такой: при тестировании я сделал по 30 прогонов в релизной конфигурации. TestA работает быстрее с классами A и B. Затем я поменял местами тест, который выполняется первым, т. е. поменял местами циклы for. В этом случае TestA все равно работает быстрее в обоих случаях. Теперь я снова поменял местами две функции и протестировал их. При тестировании с классом A TestB работает быстрее. Однако при тестировании с классом B TestA снова оказывается быстрее.

Ccre 16.08.2024 10:25

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

Martin Brown 16.08.2024 10:27

Да, я тоже это проверял. В общем, первый тест обычно выполняется быстрее, а не медленнее.

Ccre 16.08.2024 10:43

Векторы будут выполнять множество операций по распределению кучи, и, конечно же, при первом запуске состояние кучи будет другим. Итак, вы тестируете кучу или макросы? :-)

BoP 16.08.2024 11:05

Вы можете запустить препроцессор самостоятельно, используя cl /P в файле. Это поможет вам увидеть, есть ли какие-либо различия, которые вы, возможно, не учли, и, безусловно, должно включать в себя возможность того, что наличие макросов каким-либо образом влияет на скорость конечной программы. Теоретически компилятор C++ может попытаться что-то сделать с макросами в файле, который не был предварительно обработан, но я был бы очень удивлен, если бы Visual C++ (или любой другой компилятор C++) это сделал.

Cubic 16.08.2024 11:16

Я имею в виду, (endB.QuadPart - startB.QuadPart) / frequency.QuadPart это ерунда

Yakk - Adam Nevraumont 19.08.2024 19:49
testA и testB должны быть шаблонами типа T, чтобы уменьшить вероятность опечаток. a.getcontainer().push_back(Foo());, наверное, должно быть emplace_back().
Yakk - Adam Nevraumont 19.08.2024 19:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
71
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш коллега не знает, о чем говорит.

Макросы — это текстовая замена в препроцессоре, один из самых ранних этапов компиляции. Фактический компилятор видит идентичный код. Любая разница в скорости будет обусловлена ​​другими факторами. Как отмечается в комментариях, плохая методология тестирования почти наверняка является лучшим объяснением (особенно с учетом отсутствия знаний, показанного как в заявлении, так и в использовании VS 2010).

Я согласен. Я также озадачен, почему они застряли на такой устаревшей версии компилятора.

Martin Brown 16.08.2024 22:11

Другие вопросы по теме