Оптимизация (перекомпиляция) наследования виртуальных методов для каждого производного класса

Допустим, у нас есть «главный» класс с методом «Bulk» для выполнения N взаимодействий с виртуальным методом.

Этот виртуальный метод может быть переопределен многими классами, но только один раз. Из соображений производительности мы должны минимизировать стоимость вызова/разрешения vtable настолько, насколько это возможно. (Пример: генерация сетевых пакетов ++10Gb)

Одна из моих идей по решению этой проблемы заключалась в том, чтобы сделать метод Bulk виртуальным и «каким-то образом» заставить его перекомпилироваться в каждом производном классе, чтобы мы могли выполнять только один поиск VTABLE вместо N, а также получить некоторые улучшения от inlining/SSE/etc. . Однако, читая de ASM, я получаю только общий метод «Bulk», который снова выполняет поиск в vtable N раз.

¿Знаете ли вы какой-либо способ принудительной перекомпиляции этого метода (конечно, без необходимости копировать и вставлять его код в каждый производный класс) или какой-либо другой способ уменьшить количество вызовов и поиск VTABLE? Я думал, что подобные требования следует задавать часто, но я ничего не нашел...

Пример кода для игры:

master.hpp

#pragma once
#include <string>

class master
{
public:
    virtual unsigned Bulk(unsigned n)
    {
        unsigned ret = 0;
        for (int i = 0; i < 144; ++i)
            ret += once();

        return ret;
    }

    virtual unsigned once() = 0;
};

производный1.hpp

#pragma once
#include "master.hpp"

class derived1 final: public master
{
    virtual inline unsigned once() final { return 7; }
};

производный2.hpp

#pragma once
#include "master.hpp"

class derived2 final: public master
{
    virtual inline unsigned once() final { return 5; }
};

main.cpp

#include "derived1.hpp"
#include "derived2.hpp"
#include <iostream>
using namespace std;

int main()
{
    derived1 d1;
    derived2 d2;

    cout << d1.Bulk(144) << endl;
    cout << d2.Bulk(144) << endl;

    return 0;
}

Скомпилируйте cmd, который я использую: g++ main.cpp -S -O3 --std=gnu++17

Скомпилированный массовый цикл:

    movq    0(%rbp), %rax
    movq    %rbp, %rdi
    call    *8(%rax)
    addl    %eax, %r12d
    subl    $1, %ebx
    jne .L2

действительно ли once нужен как public ? или это только называется Bulk ? Если это последнее, может быть, ему вообще не нужна виртуализация?

463035818_is_not_a_number 17.12.2020 12:12

Вызов виртуальной функции почти не требует производительности (она просто будет читать указатель еще дважды). И разумный компилятор не будет повторно читать указатель при повторном вызове виртуальной функции.

Sprite 17.12.2020 12:15

Спасибо за ваш комментарий @largest_prime_is_463035818. Нет проблем с make once private, однако, если он не виртуальный, не будет никакого способа правильно вызвать его из Bulk

Ralequi 17.12.2020 12:24

Спасибо за ваш комментарий @Sprite. Я отредактировал сообщение и включил код ASM цикла. Если я не ошибаюсь, я думаю, что он проверяет VTABLE при каждой итерации. Кроме того, в любом случае явный «вызов» не позволяет проводить дальнейшую оптимизацию.

Ralequi 17.12.2020 12:37
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
4
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Я не очень понимаю ваш вопрос ;)

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

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

#include <iostream>
#include <string>
using namespace std;

struct base {
    virtual std::string Bulk(unsigned n) = 0;
    virtual ~base(){}
};

template <typename T>
struct master : base {
    virtual std::string Bulk(unsigned n) {
        std::string ret = "";
        auto ptr = static_cast<T*>(this);
        for (int i = 0; i < n; ++i) ret += ptr->once();
        return ret;
    }
};

struct derived1 final : public master<derived1> {
    std::string once() { return "a"; }
};

struct derived2 final : public master<derived2> {
    std::string once() { return "b"; }
};

int main()
{
    derived1 d1;
    derived2 d2;

    cout << d1.Bulk(3) << endl;
    cout << d2.Bulk(3) << endl;
}

Я проверяю и играю с вашим кодом, но то, что я вижу на данный момент, производит именно тот ASM, который мне нужен. Большое спасибо!

Ralequi 17.12.2020 13:02

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