Професійний спосіб створити велику проблему без заповнення величезних масивів: 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 В [i] с.

Як би я видалив / маніпулював частину масиву, щоб пам'ять знову могла використовуватись (скажімо V [0] після першої частини прикладу, де вона більше не потрібна)? Чи є альтернативи, як структурувати таку програму?

Я чув про malloc / free, але чув, що їх не слід використовувати в C ++ і що є кращі альтернативи.

Дуже дякую!

tldr; що робити з частинами масивів (окремими елементами), які мені більше не потрібні, що займають величезну кількість пам'яті?


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
Цифровий буфер дійсно, здається, саме те, що я шукаю! Зараз я встановив збільшити C ++ бібліотеки і включив boost / circular_buffer.hpp, і працює як слід. Дякую, @John
Drummermean

2
лише дуже короткі фільтри FIR реалізуються в прямому вигляді в програмному забезпеченні, а SMA майже ніколи не є.
Стів Кокс

@SteveCox: Формула краю вікна, яку ви використовували, досить ефективна для цілих фільтрів і фільтрів з фіксованою точкою, однак це неправильно для плаваючої точки, де операції не є комутаційними.
Бен Войгт

@BenVoigt Я думаю, ти мав намір відповісти на мій інший коментар, але так, ця форма запроваджує граничний цикл навколо квантування, який може бути дуже складним. на щастя, цей конкретний граничний цикл виявляється стабільним.
Стів Кокс

Вам не дуже потрібен прискорений круговий буфер для цього використання uu Ви будете використовувати набагато більше пам'яті, ніж потрібно.
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";
}

Потоки не завжди застосовні (вони не працюють, коли вам потрібен випадковий доступ до даних), але коли вони є, вони гойдаються: працюючи на дуже невеликій кількості пам'яті, ви зберігаєте все це в кеш-процесорі.


Ще одна примітка: схоже, що ваша проблема може бути "бентежно паралельною", ви можете розділити великий файл на шматки (майте на увазі, для обробки Windows 5, що вам потрібно мати 4 загальних елемента на кожному кордоні) а потім обробити шматки паралельно.

Якщо CPU - це вузьке місце (а не введення-виведення), то ви можете прискорити його, запустивши один процес на ядро, який у вас після розбиття файлів приблизно в однаковій кількості.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.