Надлежащее векторное управление памятью

Я делаю игру, и у меня есть вектор суетящихся маркеров. Когда маркер закончен, я делаю bullets.erase (bullets.begin () + i); Затем маркер исчезает. Однако это делает notseem для получения стержня памяти. Если я создаю 5 000 маркеров, затем создают еще 5,000 после того, как они вымирают, память остается такой же, но если я создам еще 5,000, в то время как те 5000 летят, она выделит новое место. К чему я должен сделать на самом деле свободный эта память?

8
задан Cœur 30 January 2019 в 02:51
поделиться

6 ответов

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

std :: vector имеет два важных понятия «размер». Во-первых, это «зарезервированный» размер, то есть объем памяти, выделенный системой для использования для хранения векторных элементов. Второй - это «используемый» размер, который логически определяет количество элементов в векторе. Ясно, что зарезервированный размер должен быть не меньше используемого. Вы можете узнать используемый размер с помощью метода size () (который, я уверен, вы уже знаете), и вы можете узнать зарезервированный размер с помощью метода capacity () .

Обычно, когда используемые и зарезервированные размеры совпадают, и вы пытаетесь вставить новый элемент, вектор выделяет новый внутренний буфер, в два раза превышающий предыдущий зарезервированный размер, и копирует все существующие элементы в этот буфер. Это прозрачно для вас, за исключением того, что аннулирует любые итераторы, которые вы держите. Как я уже отмечал ранее, AFAIK, большинство реализаций STL никогда не уменьшают зарезервированный размер в ответ на стирание.

К сожалению, хотя вы можете заставить вектор увеличить его зарезервированный размер с помощью метода reserve () , это не работает для уменьшения зарезервированной емкости .Насколько я могу судить, лучший вариант для уменьшения емкости - это сделать следующее:

std::vector<Bullet>(myVector).swap(myVector);

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

Поскольку создание этой временной копии является относительно дорогостоящей операцией (т. Е. Занимает гораздо больше процессорного времени, чем обычные операции чтения / вставки / удаления), вы не хотите делать это каждый раз, когда стираете элемент. По той же причине, поэтому вектор удваивает свой зарезервированный размер, а не увеличивает его на 1, когда вам нужно превысить существующий размер. Поэтому я бы порекомендовал, чтобы после того, как вы удалили относительно большое количество элементов и знаете, что в ближайшее время не добавите еще столько элементов, выполните описанный выше трюк с подкачкой, чтобы уменьшить емкость.

Наконец, вы также можете рассмотреть возможность использования для этого чего-то другого, кроме std :: vector . Удаление элементов из середины вектора, которое, кажется, вы делаете часто, является медленной операцией по сравнению со многими другими типами структур данных (поскольку вектор должен скопировать все последующие элементы обратно на один слот, чтобы заполнить дыру) . Какая структура данных лучше всего подходит для ваших целей, зависит от того, что еще вы делаете с данными.

15
ответ дан 5 December 2019 в 06:09
поделиться

Простой прямой ответ состоит в том, чтобы сделать ваш класс модульного теста другом тестируемого класса. Таким образом, класс модульного теста может получить доступ к calculateChecksum () , даже если он является частным.

Еще одна возможность взглянуть на то, что Foo, по-видимому, имеет ряд несвязанных обязанностей, и может быть причитается за рефакторинг. Вполне возможно, что вычисление контрольной суммы вообще не должно быть частью Foo . Вместо этого, вычисление контрольной суммы может быть лучше как алгоритм общего назначения, который любой может применить по мере необходимости (или, возможно, вид обратного - функтор для использования с другим алгоритмом, как std:: accumate ).

-121--4349383-

Это не прямой ответ на вопрос, но в моей компании мы используем IncrediBuild для распределенной компиляции. Это действительно ускоряет процесс компиляции. http://incredibuild.com/visual_studio.htm

-121--1066994-

Во-первых, метод std:: vector erase не очень эффективен, он должен перемещать все предметы после удаленного. Если порядок векторных предметов (пуль) не имеет значения, замена удаленного пуль на последний пуль и удаление последнего пуль будет быстрее (так вы получите постоянную сложность вместо линейной сложности).

Во-вторых, какова реальная проблема - что после удаления 10 000 предметов память не освобождается? Речь идет о свободной памяти, сообщаемой операционной системой, или о свободном пространстве в куче? Возможно (и весьма вероятно), что какой-то другой объект был выделен после положения данных вектора, поэтому просто освободить эту память операционной системе не представляется возможным; но его можно повторно использовать для других вновь созданных объектов.

4
ответ дан 5 December 2019 в 06:09
поделиться

Предлагаю вам взглянуть на эти две идиомы и выбрать ту, которая вам больше всего подходит:
Сжать по размеру
Очистить и минимизировать

3
ответ дан 5 December 2019 в 06:09
поделиться

Вот как обычно ведет себя модель распределения памяти вектора, чтобы обеспечить операцию амортизированного постоянного времени push_back . В основном она пытается угадать, что вы можете захотеть заполнить стертую часть новым элементом, чтобы она не освободить память. Таким образом можно избежать постоянного выделения и освобождения. Чтобы обойти это, вы можете использовать трюк подкачки, чтобы освободить неиспользуемую векторную память. Вы должны заменить пустой вектор временным безымянным вектором, чтобы, когда временный вектор выходит за пределы области видимости, он освобождает память в его деструкторе, что-то вроде: vector (c) .swap (c)

3
ответ дан 5 December 2019 в 06:09
поделиться

Это может не избавить от памяти.
Но в следующий раз, когда вам нужно будет добавить бык, перераспределять дополнительное пространство не потребуется.
Он не будет повторно использовать память, из которой была удалена пуля.

Примечание:
Если вы относительно часто стираете из середины контейнера, то вектор может быть неправильным контейнером. Это потому, что если вы удалите элемент n, тогда все элементы из [n + 1, end) должны быть перемещены на одну позицию вниз в памяти.

2
ответ дан 5 December 2019 в 06:09
поделиться

Когда пуля закончена, я выполняю bullets.erase (bullets.begin () + i);

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

#include <algorithm>
#include <functional>
#include <vector>

class Bullet
{
    // ...
public:
    bool is_finished() const;
};

int main()
{
    std::vector<Bullet> bullets;
    // ...
    bullets.erase(
        std::remove_if(
            bullets.begin(),
            bullets.end(),
            std::mem_fun_ref(&Bullet::is_finished)
        ),
        bullets.end()
    );
}

При таком подходе каждая активная пуля перемещается не более одного раза.

0
ответ дан 5 December 2019 в 06:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: