Оптимизация управления памятью в c ++ - PullRequest
0 голосов
/ 02 марта 2020

Я пытаюсь реализовать серию преобразований. Объекты, которые представляют до и после преобразования, являются классом A и классом B соответственно для демонстрации примера с минимизацией сложности. Другими словами, класс A может быть преобразован в класс B, а класс B может быть обратно преобразован в класс A. Более того, контейнер данных, использующий std :: unique_ptr в реализации для класса A и класса B, извлекается в класс Base. Базовый класс показан ниже:

class Base
{
public:
    Base()                                                                  //  Default constructor
    {
        this->size = 0;
    }

    ~Base()
    {
    }

    Base(int input_size, float input_value)                                 //  Constructor
    {
        this->size = input_size;
        this->data = std::make_unique<float[]>(input_size);
        for (int loop_number = 0; loop_number < size; loop_number++) {
            data[loop_number] = input_value;
        }
    }
    std::unique_ptr<float[]> get_data()
    {
        //  deep copy
        auto return_data = std::make_unique<float[]>(size);
        for (int loop_number = 0; loop_number < size; loop_number++) {
            return_data[loop_number] = data[loop_number];
        }
        return return_data;
    }
    int get_size()
    {
        return this->size;
    }
protected:
    int size;
    std::unique_ptr<float[]> data;
};

Далее класс A и класс B наследуют класс Base.

class B;

class A : public Base
{
public:
    A(int input_size, std::unique_ptr<int[]> const& input_data)                 //  constructor
    {
        std::cout << "Object A " << std::to_address(this) << " constructed.\n"; //  for observation
        this->size = input_size;
        this->data = std::make_unique<float[]>(this->size);
        for (int loop_number = 0; loop_number < input_size; loop_number++)
        {
            this->data[loop_number] = input_data[loop_number];                  //  Deep copy
        }
    }

    A& operator=(A const& InputImage)                                           //  Copy Assign
    {
        this->size = InputImage.size;
        for (int loop_number = 0; loop_number < this->size; loop_number++)
        {
            this->data[loop_number] = InputImage.data[loop_number];             //  Deep copy
        }
        return *this;
    }

    ~A()
    {
        std::cout << "Object A " << std::to_address(this) << " destructed.\n";  //  for observation
    }
    B to_B();

private:
    int transform_to_B(int input_value)
    {
        return std::cos(input_value); // For example
    }

};

class B : public Base
{
public:
    B(int input_size, std::unique_ptr<int[]> const& input_data)                 //  constructor
    {
        std::cout << "Object B " << std::to_address(this) << " constructed.\n"; //  for observation
        this->size = input_size;
        this->data = std::make_unique<float[]>(this->size);
        for (int loop_number = 0; loop_number < input_size; loop_number++)
        {
            this->data[loop_number] = input_data[loop_number];                  //  Deep copy
        }
    }
    auto to_A()
    {
        std::unique_ptr<int[]> transformed_data = std::make_unique<int[]>(this->size);
        for (int loop_number = 0; loop_number < this->size; loop_number++) {
            transformed_data[loop_number] = transform_to_A(this->data[loop_number]);
        }
        return A(this->size, transformed_data);
    }
    ~B()
    {
        std::cout << "Object B " << std::to_address(this) << " destructed.\n";  //  for observation
    }
private:
    int transform_to_A(int input_value)
    {
        return std::acos(input_value); // For example
    }
};

B A::to_B()
{
    std::unique_ptr<int[]> transformed_data = std::make_unique<int[]>(this->size);
    for (int loop_number = 0; loop_number < this->size; loop_number++) {
        transformed_data[loop_number] = transform_to_B(this->data[loop_number]);
    }
    return B(this->size, transformed_data);
}

Основная функция предназначена для проверки результата преобразования класс A и класс B.

int main()
{
    const int size_for_testing = 3840 * 2160;
    auto data_for_testing = std::make_unique<int[]>(size_for_testing);
    for (int loop_number = 0; loop_number < size_for_testing; loop_number++) {
        data_for_testing[loop_number] = 1;                              //  for example
    }
    A a_object(size_for_testing, data_for_testing);

    for (int loop_times = 0; loop_times < 1000; loop_times++)           //  for observation
    {
        //  version 1
        a_object = a_object.to_B().to_A().to_B().to_A().to_B().to_A();
    }
    return 0;
}

Вывод консоли

Object A 00000038FC19FE28 constructed.
Object B 00000038FC19FE10 constructed.
Object A 00000038FC19FE00 constructed.
Object B 00000038FC19FDF0 constructed.
Object A 00000038FC19FDE0 constructed.
Object B 00000038FC19FDD0 constructed.
Object A 00000038FC19FDC0 constructed.
Object A 00000038FC19FDC0 destructed.
Object B 00000038FC19FDD0 destructed.
Object A 00000038FC19FDE0 destructed.
Object B 00000038FC19FDF0 destructed.
Object A 00000038FC19FE00 destructed.
Object B 00000038FC19FE10 destructed.
Object B 00000038FC19FE10 constructed.
Object A 00000038FC19FE00 constructed.
Object B 00000038FC19FDF0 constructed.
......

Я знаю, что имеет смысл, что среднесрочные объекты, созданные для обработки, освобождены в конце области действия для l oop. Однако, если возникает более сложный случай, такой как a_object = a_object.to_B().to_A().to_B().to_A().to_B().to_A().to_B().to_A().to_B().to_A().to_B().to_A();, это может привести к огромному потреблению памяти при хранении этих промежуточных объектов. Мне любопытно, что концепция или философия проектирования «освобождают объект в конце области, где он был объявлен». Возможно, его можно оптимизировать в зависимости от использования.

С другой стороны, использование памяти отдельной формы, показанной ниже, аналогично версии 1.

        //  version 2
        auto temp1 = a_object.to_B();
        auto temp2 = temp1.to_A();
        auto temp3 = temp2.to_B();
        auto temp4 = temp3.to_A();
        auto temp5 = temp4.to_B();
        a_object = temp5.to_A();

Чтобы попытаться уменьшить потребление памяти, лямбда-выражение также рассматривается как ниже. Однако использование памяти также аналогично версии 1.

        //  version 3
        auto temp1 = [](auto& input_object) { return input_object.to_B(); }(a_object);
        auto temp2 = [](auto& input_object) { return input_object.to_A(); }(temp1);
        auto temp3 = [](auto& input_object) { return input_object.to_B(); }(temp2);
        auto temp4 = [](auto& input_object) { return input_object.to_A(); }(temp3);
        auto temp5 = [](auto& input_object) { return input_object.to_B(); }(temp4);
        auto a_object = [](auto& input_object) { return input_object.to_A(); }(temp5);

Кстати, кажется, что такого рода лямбда-выражения нельзя объединить, как показано ниже. Компилятор выдает ошибку C2664 и говорит: «auto main :::: operator () (_ T1 &) const»: невозможно преобразовать аргумент 1 из «B» в «_T1 &»

a_object = [](auto& input_object) { return input_object.to_A(); }(
                        [](auto& input_object) { return input_object.to_B(); }(
                        [](auto& input_object) { return input_object.to_A(); }(
                        [](auto& input_object) { return input_object.to_B(); }(
                        [](auto& input_object) { return input_object.to_A(); }(
                        [](auto& input_object) { return input_object.to_B(); }(a_object))))));

Наконец, мой вопросы:

1) Мне любопытно, что концепция или философия конструирования «освобождают объект в конце области, где он был объявлен». Возможно, его можно оптимизировать в зависимости от использования.

2) Есть ли лучший способ уменьшить потребление памяти для такого рода каскадной структуры, например, более сложный случай a_object = a_object.to_B().to_A().to_B().to_A().to_B().to_A().to_B().to_A().to_B().to_A().to_B().to_A();?

Среда:

Процессор: Intel® Core ™ i7-6700HQ 2,6 ГГц

Оперативная память: 16 ГБ

ОС: Windows 10 1909

IDE: Microsoft Visual Studio Community 2019 версия 16.4.5

1 Ответ

2 голосов
/ 02 марта 2020

1) Мне любопытно, что концепция или философия конструирования "освобождают объект в конце области, в которой он был объявлен". Возможно, его можно оптимизировать в зависимости от использования.

Это позволяет "безопасно" использовать временное значение этого оператора.

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

2) Есть ли лучший способ уменьшить потребление памяти для такого рода каскадной структуры, такой как более сложный случай a_object = a_object.to_B .......... () .to_A () to_B () to_A () to_B () to_A () to_B () to_A () to_B () to_A () to_B () to_A ();?

Вы можете добавить временную перегрузку:

B A::to_B() &&
{
    std::unique_ptr<int[]> transformed_data = std::make_unique<int[]>(this->size);
    for (int loop_number = 0; loop_number < this->size; loop_number++) {
        transformed_data[loop_number] = transform_to_B(this->data[loop_number]);
    }
    auto Bsize = this->size;
    this->size = 0;
    this->data.reset(); // We clear here.
                        // Implementation might even really transfer the buffer
    return B(Bsize, std::move(transformed_data));
}

...