Modo professionale per produrre un grosso problema senza riempire enormi matrici: C ++, memoria libera da parte di un array


20

Sto sviluppando una simulazione fisica e, dato che sono piuttosto nuovo nella programmazione, continuo a riscontrare problemi quando produco programmi di grandi dimensioni (principalmente problemi di memoria). Conosco l'allocazione e l'eliminazione dinamica della memoria (nuovo / elimina, ecc.), Ma ho bisogno di un approccio migliore a come strutturare il programma.

Diciamo che sto simulando un esperimento che sta funzionando da alcuni giorni, con una frequenza di campionamento molto grande. Avrei bisogno di simulare un miliardo di campioni e passarci sopra.

Come versione super semplificata, diremo che un programma prende tensioni V [i] e le somma in cinque:

cioè NewV [0] = V [0] + V [1] + V [2] + V [3] + V [4]

quindi NewV [1] = V [1] + V [2] + V [3] + V [4] + V [5]

quindi NewV [2] = V [2] + V [3] + V [4] + V [5] + V [6] ... e questo continua per un miliardo di campioni.

Alla fine, avrei V [0], V [1], ..., V [1000000000], quando invece gli unici che dovrei memorizzare per il passaggio successivo sono gli ultimi 5 V [i] S.

Come potrei eliminare / deallocare parte dell'array in modo che la memoria sia libera di riutilizzarla (diciamo V [0] dopo la prima parte dell'esempio in cui non è più necessaria)? Esistono alternative a come strutturare un tale programma?

Ho sentito parlare di malloc / free, ma ho sentito che non dovrebbero essere usati in C ++ e che ci sono alternative migliori.

Grazie mille!

TLDR; cosa fare con parti di array (singoli elementi) che non mi servono più e che occupano un'enorme quantità di memoria?


2
Non è possibile deallocare parte di un array. Potresti riassegnarlo ad un array più piccolo altrove, ma questo potrebbe rivelarsi costoso. In alternativa, è possibile utilizzare una struttura dati diversa come un elenco collegato. Forse potresti anche memorizzare i passaggi Vanziché in un nuovo array. Fondamentalmente, tuttavia, penso che il tuo problema sia nei tuoi algoritmi o nelle tue strutture di dati, e poiché non abbiamo dettagli, è difficile sapere come farlo in modo efficiente.
Vincent Savard,

4
Nota a margine: le SMA di lunghezza arbitraria possono essere calcolate in modo particolarmente veloce con questa relazione di ricorrenza: NewV [n] = NewV [n-1] - V [n-1] + V [n + 4] (la tua notazione). Ma tieni presente che questi non sono filtri particolarmente utili. La loro risposta in frequenza è sincera, che non è praticamente mai ciò che si desidera (bande laterali molto alte).
Steve Cox,

2
SMA = media mobile semplice, per chiunque si chieda.
Charles

3
@SteveCox, nel modo in cui l'ha scritto, ha un filtro FIR. La tua ricorrenza è il modulo IIR equivalente. Ad ogni modo, puoi mantenere un buffer circolare delle ultime N letture.
John R. Strohm,

@ JohnR.Strohm la risposta all'impulso è identica e limitata
Steve Cox,

Risposte:


58

Quello che descrivi, "smoothing by cinque", è un filtro digitale FIR (risposta all'impulso). Tali filtri sono implementati con buffer circolari. Mantenete solo gli ultimi N valori, mantenete un indice nel buffer che vi dice dove si trova il valore più vecchio, sovrascrivete il valore più vecchio corrente con il più recente ad ogni passaggio e spostate l'indice, in modo circolare, ogni volta.

Conserva i dati raccolti, che hai intenzione di ridurre, sul disco.

A seconda del tuo ambiente, questo potrebbe essere uno di quei posti in cui è meglio ottenere un aiuto esperto. All'università, hai messo un appunto sulla bacheca del Dipartimento di Informatica, che offre i salari degli studenti (o anche i tassi di consulenza degli studenti) per alcune ore di lavoro, per aiutarti a sgretolare i tuoi dati. O forse offri punti Opportunità di ricerca universitaria. O qualcosa.


6
Un buffer circolare sembra davvero quello che sto cercando! Ora ho installato le librerie boost C ++ e ho incluso boost / circular_buffer.hpp e funziona come previsto. Grazie, @John
Drummermean,

2
solo filtri FIR molto brevi sono implementati in forma diretta nel software, e quasi mai lo sono SMA.
Steve Cox,

@SteveCox: la formula dei bordi della finestra che hai usato è abbastanza efficace per i filtri interi e a virgola fissa, tuttavia non è corretta per i virgola mobile, dove le operazioni non sono commutative.
Ben Voigt,

@BenVoigt penso che intendessi rispondere al mio altro commento, ma sì, quella forma introduce un ciclo limite attorno a una quantizzazione che può essere molto complicato. per fortuna, però, questo particolare ciclo limite risulta stabile.
Steve Cox,

Non hai davvero bisogno di boost per un buffer circolare per quell'uso. Userai molta più memoria del necessario.
GameDeveloper

13

Ogni problema può essere risolto aggiungendo un ulteriore livello di riferimento indiretto. Quindi fallo.

Non è possibile eliminare parte di un array in C ++. Ma puoi creare un nuovo array contenente solo i dati che vuoi conservare, quindi eliminare quello vecchio. Quindi puoi costruire una struttura di dati che ti permetta di "rimuovere" gli elementi che non vuoi che vengano visualizzati. Ciò che effettivamente farà è creare un nuovo array e copiare gli elementi non rimossi in quello nuovo, quindi eliminare quello vecchio.

Oppure potresti semplicemente usare std::deque, il che può effettivamente farlo già. deque, o "coda doppia", è una struttura di dati destinata ai casi in cui si eliminano elementi da un'estremità mentre si aggiungono elementi all'altra.


30
Ogni problema può essere risolto aggiungendo un ulteriore livello di riferimento indiretto ... ad eccezione di molti livelli di riferimento indiretto.
YSC,

17
@YSC: and spelling :)
Lightness Races with Monica

1
per questo particolare problema std::dequeè la strada da percorrere
davidbak

7
@davidbak - What? Non è necessario allocare e rilasciare costantemente memoria. Un buffer circolare di dimensioni fisse che viene allocato una volta al momento dell'inizializzazione si adatta molto meglio a questo problema.
David Hammen,

2
@DavidHammen: Forse, ma 1) La libreria standard non ha un "buffer circolare di dimensioni fisse" nel suo toolkit. 2) Se hai davvero bisogno di tale ottimizzazione, puoi fare alcune cose sull'allocatore per minimizzare le riallocazioni deque. Cioè, archiviare e riutilizzare le allocazioni come richiesto. Quindi dequesembra una soluzione perfettamente adeguata al problema.
Nicol Bolas,

4

Le risposte FIR e SMA che hai ottenuto sono buone nel tuo caso, tuttavia vorrei cogliere l'occasione per promuovere un approccio più generico.

Quello che hai qui è un flusso di dati: invece di strutturare il tuo programma in 3 grandi passi (ottenere dati, calcolare, risultato di output) che richiedono il caricamento di tutti i dati in memoria contemporaneamente, puoi invece strutturarlo come una pipeline .

Una pipeline inizia con un flusso, lo trasforma e lo spinge in un lavandino.

Nel tuo caso, la pipeline appare come:

  1. Leggi gli elementi dal disco, emetti gli elementi uno alla volta
  2. Ricevi gli articoli uno alla volta, per ogni oggetto ricevuto emetti gli ultimi 5 ricevuti (dove arriva il tuo buffer circolare)
  3. Ricevi gli oggetti 5 alla volta, per ogni gruppo calcola il risultato
  4. Ricevi il risultato, scrivilo sul disco

Il C ++ tende a usare gli iteratori piuttosto che i flussi, ma ad essere sinceri i flussi sono più facili da modellare (esiste una proposta per intervalli che sarebbero simili ai flussi):

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;
};

E poi, la pipeline assomiglia a:

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";
}

I flussi non sono sempre applicabili (non funzionano quando è necessario un accesso casuale ai dati), ma quando lo sono, oscillano: operando su una quantità molto piccola di memoria, si conserva tutto nella cache della CPU.


In un'altra nota: sembra che il tuo problema potrebbe essere "imbarazzantemente parallelo", potresti voler dividere il tuo grande file in blocchi (tieni presente, per l'elaborazione da windows di 5, che devi avere 4 elementi comuni su ogni confine) e quindi elaborare i blocchi in parallelo.

Se la CPU è il collo di bottiglia (e non I / O), è possibile accelerarla avviando un processo per core che si ha dopo aver suddiviso i file in quantità approssimativamente uguali.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.