Профессиональный способ создания большой проблемы без заполнения огромных массивов: C ++, освобождение памяти от части массива

20

Я занимаюсь симуляцией физики, и, поскольку я довольно новичок в программировании, я продолжаю сталкиваться с проблемами при создании больших программ (в основном с памятью). Я знаю о динамическом распределении и удалении памяти (new / delete и т. Д.), Но мне нужен лучший подход к структурированию программы.

Допустим, я моделирую эксперимент, который длится несколько дней с очень большой частотой дискретизации. Мне нужно смоделировать миллиард образцов и выполнить их.

Как упрощенная версия, мы скажем, что программа берет напряжения V [i] и суммирует их в пять:

т.е. NewV [0] = V [0] + V [1] + V [2] + V [3] + V [4]

тогда NewV [1] = V [1] + V [2] + V [3] + V [4] + V [5]

затем NewV [2] = V [2] + V [3] + V [4] + V [5] + V [6] ... и это продолжается для миллиарда выборок.

В конце концов, у меня будет V [0], V [1], ..., V [1000000000], тогда как вместо этого единственные, которые мне нужно сохранить для следующего шага, это последние 5 V [i] s.

Как бы я удалил / освободил часть массива, чтобы память снова могла свободно использоваться (скажем, V [0] после первой части примера, где она больше не нужна)? Есть ли альтернативы тому, как структурировать такую ​​программу?

Я слышал о malloc / free, но слышал, что они не должны использоваться в C ++ и что есть лучшие альтернативы.

Огромное спасибо!

tldr; что делать с частями массивов (отдельными элементами), которые мне больше не нужны, которые занимают огромное количество памяти?

Drummermean
источник
2
Вы не можете освободить часть массива. Вы можете перераспределить его в меньший массив где-нибудь еще, но это может оказаться дорогостоящим. Вы можете использовать другую структуру данных, например, связанный список, возможно. Возможно, вы могли бы также сохранить шаги в Vновом массиве. В целом, однако, я думаю, что ваша проблема заключается либо в ваших алгоритмах, либо в ваших структурах данных, и, поскольку у нас нет никаких деталей, трудно понять, как это сделать эффективно.
Винсент
4
Примечание: SMA произвольной длины могут быть вычислены особенно быстро с помощью этого рекуррентного соотношения: NewV [n] = NewV [n-1] - V [n-1] + V [n + 4] (ваша запись). Но имейте в виду, что это не особенно полезные фильтры. Их частотный отклик - синк, который почти никогда не соответствует желаемым (действительно высокие боковые лепестки).
Стив Кокс
2
SMA = простая скользящая средняя, ​​для всех, кто интересуется.
Чарльз
3
@ SteveCox, как он это написал, у него есть FIR-фильтр. Ваше повторение является эквивалентной формой IIR. В любом случае, вы можете поддерживать циклический буфер последних N показаний.
Джон Р. Штром
@ JohnR.Strohm импульсный отклик идентичен и конечен
Стив Кокс

Ответы:

58

То, что вы описываете, «сглаживание на пять», представляет собой цифровой фильтр с конечной импульсной характеристикой (FIR). Такие фильтры реализованы с помощью кольцевых буферов. Вы сохраняете только последние N значений, сохраняете индекс в буфере, который сообщает вам, где находится самое старое значение, вы перезаписываете текущее самое старое значение самым новым на каждом шаге и каждый раз шагаете по индексу по кругу.

Вы храните свои собранные данные, которые собираетесь сжать, на диске.

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

Джон Р. Штром
источник
6
Похоже, мне нужен именно кольцевой буфер! Я сейчас установил библиотеки boost C ++ и включил boost / round_buffer.hpp, и работает как положено. Спасибо, @Джон
Барабанщик
2
только очень короткие фильтры FIR реализуются в программном виде напрямую, а SMA - почти никогда.
Стив Кокс
@SteveCox: Формула краев окна, которую вы использовали, довольно эффективна для целочисленных фильтров и фильтров с фиксированной запятой, однако она некорректна для операций с плавающей запятой, где операции не являются коммутативными.
Бен Фойгт
@BenVoigt Я думаю, что вы хотели ответить на мой другой комментарий, но да, эта форма вводит цикл ограничения вокруг квантования, который может быть очень сложным. к счастью, этот конкретный предельный цикл оказывается стабильным.
Стив Кокс
Вам на самом деле не нужно увеличивать циклический буфер для этого использования. Вы будете использовать гораздо больше памяти, чем необходимо.
GameDeveloper
13

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

Вы не можете удалить часть массива в C ++. Но вы можете создать новый массив, содержащий только те данные, которые вы хотите сохранить, а затем удалить старый. Таким образом, вы можете создать структуру данных, которая позволит вам «удалять» ненужные элементы спереди. Что он на самом деле сделает, так это создаст новый массив и скопирует невосстановленные элементы в новый, затем удалит старый.

Или вы можете просто использовать std::deque, который может эффективно сделать это уже. dequeили «двусторонняя очередь» - это структура данных, предназначенная для случаев, когда вы удаляете элементы с одного конца, добавляя элементы с другого.

Николь Болас
источник
30
Любая проблема может быть решена путем добавления дополнительного уровня косвенности ... за исключением многих уровней косвенности.
СМУ
17
@YSC: и орфография :)
Легкость гонок с Моникой
1
для этой конкретной проблемы std::dequeесть путь
Давидбак
7
@davidbak - Что? Нет необходимости постоянно выделять и освобождать память. Круговой буфер фиксированного размера, который выделяется один раз во время инициализации, гораздо лучше подходит для этой проблемы.
Дэвид
2
@DavidHammen: Возможно, но 1) Стандартная библиотека не имеет «кольцевого буфера фиксированного размера» в своем наборе инструментов. 2) Если вам действительно нужна такая оптимизация, вы можете сделать некоторые вещи распределителя, чтобы минимизировать перераспределение через deque. То есть хранение и повторное использование выделенных ресурсов по запросу. Так что dequeкажется вполне адекватным решением проблемы.
Николь Болас
4

Ответы FIR и SMA, которые вы получили, хороши в вашем случае, однако я хотел бы воспользоваться возможностью для продвижения более общего подхода.

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

Конвейер начинается с потока, преобразует его и переносит в сток.

В вашем случае конвейер выглядит так:

  1. Чтение элементов с диска, создание элементов по одному
  2. Получать элементы по одному, для каждого полученного элемента выбрасывать последние 5 полученных (где входит ваш круговой буфер)
  3. Получите элементы 5 за раз, для каждой группы вычислите результат
  4. Получите результат, запишите его на диск

C ++ имеет тенденцию использовать итераторы, а не потоки, но, честно говоря, потоки легче моделировать (есть предложение для диапазонов, которые были бы похожи на потоки):

template <typename T>
class Stream {
public:
    virtual boost::optional<T> next() = 0;
    virtual ~Stream() {}
};

class ReaderStream: public Stream<Item> {
public:
    boost::optional<Item> next() override final;

private:
    std::ifstream file;
};

class WindowStream: public Stream<Window> {
public:
    boost::optional<Window> next() override final;

private:
    Window window;
    Stream<Item>& items;
};

class ResultStream: public Stream<Result> {
public:
    boost::optional<Result> next() override final;

private:
    Stream<Window>& windows;
};

И тогда, трубопровод выглядит так:

ReaderStream itemStream("input.txt");
WindowStream windowStream(itemsStream, 5);
ResultStream resultStream(windowStream);
std::ofstream results("output.txt", std::ios::binary);

while (boost::optional<Result> result = resultStream.next()) {
    results << *result << "\n";
}

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


С другой стороны: кажется, что ваша проблема может быть «смущающей параллелью», вы можете разбить ваш большой файл на куски (имейте в виду, что для обработки по окнам из 5 вам нужно иметь 4 общих элемента на каждой границе) а затем обрабатывать куски параллельно.

Если узким местом является ЦП (а не ввод-вывод), вы можете ускорить его, запустив один процесс на ядро ​​после разделения файлов в примерно равных количествах.

Матье М.
источник