巨大な配列をいっぱいにすることなく大きな問題を生成するプロフェッショナルな方法:C ++、配列の一部からメモリを解放


20

私は物理シミュレーションを開発していますが、プログラミングが初めてなので、大規模なプログラムを作成するときに問題が発生し続けます(主にメモリの問題)。動的メモリの割り当てと削除(新規/削除など)については知っていますが、プログラムをどのように構成するかについて、より良いアプローチが必要です。

非常に大きなサンプリングレートで、数日間実行されている実験をシミュレートしているとしましょう。10億個のサンプルをシミュレートし、それらを実行する必要があります。

非常に単純化されたバージョンとして、プログラムは電圧V [i]を取り、それらを5で合計すると言います。

すなわち、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] ...そして、これは10億個のサンプルで続きます。

最終的には、V [0]、V [1]、...、V [1000000000]になります。代わりに、次のステップで保存する必要があるのは最後の5 V [i]だけですs。

メモリが再び自由に使用できるように、配列の一部を削除/割り当て解除するにはどうすればよいですか(例の最初の部分が不要になった後のV [0]など)?そのようなプログラムを構成する方法に代わるものはありますか?

malloc / freeについて聞いたことがありますが、C ++で使用すべきではなく、より良い代替手段があると聞きました。

どうもありがとう!

tldr; 配列の一部(個々の要素)をどうすればいいですか?


2
配列の一部の割り当てを解除することはできません。別の場所にある小さな配列に再割り当てすることもできますが、これには費用がかかることがあります。代わりに、リンクリストなどの別のデータ構造を使用することもできます。V新しい配列ではなくステップを保存することもできます。基本的には、あなたの問題はあなたのアルゴリズムまたはデータ構造のいずれかにあると思います。詳細が分からないので、それを効率的に行う方法を知るのは難しいです。
ビンセントサバード

4
補足:任意の長さのSMAは、この再帰関係を使用して特に高速に計算できます。NewV[n] = NewV [n-1]-V [n-1] + V [n + 4](表記)。ただし、これらは特に有用なフィルターではないことに注意してください。それらの周波数応答はsincであり、これはあなたが望むものとはまったく違います(本当に高いサイドローブ)。
スティーブコックス

2
SMA =単純な移動平均、不思議な人には。
チャールズ

3
@SteveCox、彼が書いたように、彼はFIRフィルターを持っています。繰り返しは同等のIIRフォームです。どちらにしても、最後のN個の読み取り値の循環バッファーを維持できます。
ジョンR.ストローム

@ JohnR.Strohmインパルス応答は同一であり、有限です
スティーブコックス

回答:


58

「スムージングバイファイブ」とは、有限インパルス応答(FIR)デジタルフィルターのことです。このようなフィルターは、循環バッファーで実装されます。最後のN個の値のみを保持し、最も古い値がどこにあるかを示すインデックスをバッファーに保持し、各ステップで現在の最も古い値を最新の値で上書きし、そのたびにインデックスを循環的にステップします。

収集したデータは、ディスクに保存します。

環境によっては、これは経験豊富なヘルプを得ることをお勧めします。大学では、コンピューターサイエンス部門の掲示板にメモを書き、数時間の学生の賃金(または学生のコンサルティングレート)を提供して、データの処理を支援します。または、学部生研究の機会ポイントを提供するかもしれません。か何か。


6
実際、循環バッファーは私が探しているもののようです!ブーストC ++ライブラリをインストールし、boost / circular_buffer.hppをインクルードしましたが、期待どおりに機能しています。ありがとう、@ John
ドラマー平均

2
非常に短いFIRフィルタのみがソフトウェアに直接実装されており、SMAはほとんど実装されていません。
スティーブコックス

@SteveCox:使用したウィンドウのエッジ式は、整数フィルターと固定小数点フィルターに対して非常に効果的ですが、操作が可換ではない浮動小数点に対しては正しくありません。
ベンフォークト

@BenVoigtあなたは私の他のコメントに返信するつもりだったと思いますが、はい、そのフォームは非常にトリッキーな量子化の周りに制限サイクルを導入します。ありがたいことに、この特定の制限サイクルはたまたま安定しています。
スティーブコックス

uuを使用するための循環バッファのブーストは、実際には必要ありません。必要以上のメモリを使用することになります。
GameDeveloper

13

すべての問題は、間接レベルを追加することで解決できます。だからそうする。

C ++では配列の一部を削除できません。ただし、保持するデータのみを保持する新しい配列を作成してから、古い配列を削除することができます。したがって、不要な要素を前面から「削除」できるデータ構造を構築できます。実際に行うことは、新しい配列を作成し、削除されていない要素を新しい要素にコピーしてから、古い要素を削除することです。

またはstd::deque、を使用するだけで、既にこれを効果的に実行できます。deque、または「両端キュー」は、一方の端から要素を削除し、もう一方の端に要素を追加する場合に使用するデータ構造です。


30
すべての問題は、多くのレベルのインダイレクションを除き、追加のレベルのインダイレクションを追加することで解決できます
YSC

17
@YSC:およびスペル:)
モニカ

1
この特定の問題std::dequeのために行く方法です
-davidbak

7
@davidbak-何?メモリを常に割り当てたり解放したりする必要はありません。初期化時に一度割り当てられる固定サイズの循環バッファは、この問題によりよく適合します。
デビッドハンメン

2
@DavidHammen:おそらくですが、1)標準ライブラリのツールキットには「固定サイズの循環バッファー」がありません。2)そのような最適化が本当に必要な場合は、いくつかのアロケーターを実行して、再割り当てを最小限に抑えることができますdeque。つまり、要求に応じて割り当てを保存および再利用します。したがってdeque、問題に対する完全に適切な解決策のようです。
ニコルボーラス

4

あなたが得たFIRとSMAの回答はあなたの場合には良いですが、より一般的なアプローチを推し進める機会を得たいと思います。

ここにあるのはデータのストリームです:一度にすべてのデータをメモリに読み込む必要がある3つの大きなステップ(データの取得、計算、出力結果)でプログラムを構造化する代わりに、パイプラインとして構造化できます。

パイプラインは、ストリームで始まり、それを変換し、シンクにプッシュします。

あなたの場合、パイプラインは次のようになります。

  1. ディスクからアイテムを読み取り、一度に1つずつアイテムを発行します
  2. アイテムを1つずつ受信します。受信した各アイテムについて、最後に受信した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";
}

ストリームは常に適用できるとは限りません(データへのランダムアクセスが必要な場合は動作しません)が、ストリームは揺らぎます:非常に少量のメモリを操作することにより、すべてをCPUキャッシュに保持します。


別のメモ:あなたの問題は「恥ずかしいほど平行」であるように思えます、あなたは大きなファイルをチャンクに分割したいかもしれませんそして、チャンクを並行して処理します。

CPUが(I / Oではなく)ボトルネックの場合、ファイルをほぼ同じ量に分割した後、コアごとに1つのプロセスを起動することで、CPUを高速化できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.