Уменьшение потребления памяти в C++

Я пишу заголовочный файл на C++, реализующий взаимодействие с кватернионом. Кватернион можно интерпретировать как a + bi + cj + dk, поэтому его компоненты хранятся в m_value в порядке b, c, d, a. Я также поддерживаю представление кватерниона в виде скаляра и вектора. Я заметил, что в методе updateComponents() я использую в 2 раза больше памяти, чем мне действительно нужно. Как я могу это исправить? m_value и требуется скалярное и векторное представление.

#ifndef QUAT_HPP
#define QUAT_HPP

#include <cmath>

template<typename T>
struct matrix_t {
    T data[16];
};

template<typename T>
struct vector3_t {
    T x, y, z;
};

template<typename T>
class Quat {
public:
    Quat() : scalar(0), vector{0, 0, 0} {
        updateComponents();
    }

    Quat(T a, T b, T c, T d) : scalar(a), vector{b, c, d} {
        updateComponents();
    }

    Quat(T angle, bool mode, vector3_t<T> axes) {
        if (!mode) {
            angle = angle * M_PI / 180;
        }
        T norm = std::sqrt(axes.x * axes.x + axes.y * axes.y + axes.z * axes.z);
        scalar = std::cos(angle / 2);
        vector.x = axes.x / norm * std::sin(angle / 2);
        vector.y = axes.y / norm * std::sin(angle / 2);
        vector.z = axes.z / norm * std::sin(angle / 2);
        updateComponents();
    }

    const T *data() const { return m_value; };

    Quat<T> operator+(const Quat<T> &q2) const {
        return Quat(scalar + q2.scalar, vector.x + q2.vector.x, vector.y + q2.vector.y, vector.z + q2.vector.z);
    }

    Quat<T> &operator+=(const Quat<T> &q2) {
        *this = *this + q2;
        updateComponents();
        return *this;
    }

    Quat<T> operator-(Quat q2) const {
        return Quat(scalar - q2.scalar, vector.x - q2.vector.x, vector.y - q2.vector.y, vector.z - q2.vector.z);
    }

    Quat<T> &operator-=(Quat q2) {
        *this = *this - q2;
        updateComponents();
        return *this;
    }

    Quat<T> operator*(Quat q2) const {
        T a1 = scalar, b1 = vector.x, c1 = vector.y, d1 = vector.z;
        T a2 = q2.scalar, b2 = q2.vector.x, c2 = q2.vector.y, d2 = q2.vector.z;
        return Quat(
                a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2,
                a1 * b2 + a2 * b1 + c1 * d2 - c2 * d1,
                a1 * c2 + a2 * c1 + d1 * b2 - d2 * b1,
                a1 * d2 + a2 * d1 + b1 * c2 - b2 * c1);
    }

    Quat<T> operator*(T s) const { return Quat(scalar * s, vector.x * s, vector.y * s, vector.z * s); }

    Quat<T> operator*(vector3_t<T> vec) const {
        Quat<T> q2 = Quat(0, vec.x, vec.y, vec.z);
        return *this * q2;
    }

    Quat<T> operator~() const { return Quat(scalar, -vector.x, -vector.y, -vector.z); }

    bool operator==(Quat q2) const {
        return (scalar == q2.scalar && vector.x == q2.vector.x && vector.y == q2.vector.y && vector.z == q2.vector.z);
    }

    bool operator!=(Quat q2) const { return !(*this == q2); }

    explicit operator T() const {
        return std::sqrt(scalar * scalar + vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
    }

    matrix_t<T> rotation_matrix() const {
        Quat<T> q = normalize();
        T s = q.scalar, x = q.vector.x, y = q.vector.y, z = q.vector.z;
        T r_11 = 1 - 2 * y * y - 2 * z * z, r_12 = 2 * x * y - 2 * z * s, r_13 = 2 * x * z + 2 * y * s,
          r_21 = 2 * x * y + 2 * z * s, r_22 = 1 - 2 * x * x - 2 * z * z, r_23 = 2 * y * z - 2 * x * s,
          r_31 = 2 * x * z - 2 * y * s, r_32 = 2 * y * z + 2 * x * s, r_33 = 1 - 2 * x * x - 2 * y * y;
        return {r_11, r_21, r_31, 0, r_12, r_22, r_32, 0, r_13, r_23, r_33, 0, 0, 0, 0, 1};
    }

    matrix_t<T> matrix() const {
        return {scalar, -vector.x, -vector.y, -vector.z, vector.x, scalar, -vector.z, vector.y,
                vector.y, vector.z, scalar, -vector.x, vector.z, -vector.y, vector.x, scalar};
    }

    T angle(bool mode = true) const { return mode ? 2 * std::acos(scalar) : 2 * std::acos(scalar) * 180 / M_PI; }

    vector3_t<T> apply(vector3_t<T> vec) const {
        Quat<T> q = normalize();
        Quat<T> qv = q * vec;
        Quat<T> qq = qv * (~q);
        return {qq.vector.x, qq.vector.y, qq.vector.z};
    }

private:
    T m_value[4];
    T scalar;
    vector3_t<T> vector;

    void updateComponents() {
        m_value[3] = scalar;
        m_value[0] = vector.x;
        m_value[1] = vector.y;
        m_value[2] = vector.z;
    }

    Quat<T> normalize() const {
        T norm = T(*this);
        return *this * (1 / norm);
    }
};
#endif

Я читал о union, но думаю, что он не будет так эффективно использовать память, как я ожидаю.

какие допустимые значения для T ?!

Ahmed AEK 05.05.2024 14:48

Как используется показанный вами код? (например, каковы потребности программы, использующей ваш класс) Как вы определили, какая «память вам нужна»? И как вы определили объем памяти, который на самом деле использует ваша программа? Расчет использования памяти — это гораздо больше, чем просто сложение памяти, выделяемой вашей программой, или используемых вами структур данных. Память также необходима для представления самой программы (инструкций и т. д.) — объем накладных расходов зависит от вашей хост-системы, операционной системы, архитектуры набора команд и т. д. и т. п.

Peter 05.05.2024 14:48

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

PaulMcKenzie 05.05.2024 14:49

Зачем тебе вообще T m_value[4];?

NathanOliver 05.05.2024 14:49
Quat(T angle, bool mode, vector3_t<T> axes) -- Почему вы не обратили внимание на то, что это неэффективно или потребляет память? axes передается по значению, что означает, что создается полная временная копия axes. То же самое здесь: Quat<T> operator-(Quat q2) и в других вызовах функций. Вы должны передавать константную ссылку, а не значение.
PaulMcKenzie 05.05.2024 14:52

@AhmedAEK с плавающей запятой и двойной фиксацией

John 05.05.2024 14:53

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

Tanveer Badar 05.05.2024 14:55

кажется дубликатом stackoverflow.com/a/702679/15649230

Ahmed AEK 05.05.2024 14:57

@PaulMcKenzie спасибо за это замечание, я это исправил

John 05.05.2024 15:07

@NathanOliver это требуется по техническому заданию. В методе *data() мне нужно вернуть указатель на 4 компонента кватерниона.

John 05.05.2024 15:12

@Джон Тогда это единственный участник, который тебе нужен.

NathanOliver 05.05.2024 15:17

@NathanOliver Я уже думал об этом, но задача такая: «Обратите внимание, что кватернион можно представить как комбинацию скаляра и вектора из трех компонентов. Соответственно, кватернионные и скалярно-векторные операции следует рассматривать именно с этой точки зрения. ", так что я немного в замешательстве

John 05.05.2024 15:26

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

NathanOliver 05.05.2024 15:30

Есть одна реализация в стадии разработки. Он также предоставляет класс окторниона.

Red.Wave 05.05.2024 15:34

@Джон — float и double — к вашему сведению, ваша программа компилируется, если T есть std::string, но умирает при запуске. Посмотрите это. Чтобы избежать ошибки, вам необходимо реализовать проверку числовых типов во время компиляции.

PaulMcKenzie 05.05.2024 15:36
Стоит ли изучать 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
15
93
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Если вам нужно *data() вернуть то, что есть, у вас есть 2 варианта. Используйте m_value для всего, это будет выглядеть не так красиво, если использовать [0] вместо .x и т. д., но он будет делать то, что вы хотите, а в остальном код будет работать идентично. Или вы можете разобраться в тонкостях ручного управления памятью, newназначив и назначив массив, который будет m_value при каждом вызове *data(). Это могло бы очень быстро использовать гораздо больше памяти, чем то, что у вас есть сейчас, если бы вы не были осторожны с этим, но у него есть преимущество, заключающееся в том, что он не позволяет другому коду возиться с вашим частным членом, как это возможно сейчас.

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