在不填满巨大数组的情况下产生大问题的专业方法:C ++,从数组的一部分释放内存


20

我正在开发物理模拟,由于我对编程还不熟悉,因此在生成大型程序时会遇到很多问题(主要是内存问题)。我知道动态内存分配和删除(新建/删除等),但是我需要一种更好的方法来构造程序。

假设我正在模拟一个实验,该实验运行了几天,并且采样率非常高。我需要模拟十亿个样本,然后运行它们。

作为一个超级简化的版本,我们将说一个程序采用电压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] ...这样就持续了十亿个样本。

最后,我将拥有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](您的表示法)。但是请记住,这些并不是特别有用的过滤器。它们的频率响应是正弦波,这几乎不是您想要的(真正的高旁瓣)。
史蒂夫·考克斯

2
SMA =简单移动平均线,任何人都想知道。
查尔斯(Charles)

3
@SteveCox,他写的方式,他有一个FIR滤波器。重复发生是等效的IIR表格。无论哪种方式,您都可以维护最后N个读数的循环缓冲区。
约翰·斯特罗姆

@ JohnR.Strohm的脉冲响应是相同的,并且是有限的
Steve Cox

Answers:


58

您所描述的“平滑扩展”是一种有限脉冲响应(FIR)数字滤波器。此类过滤器使用循环缓冲区实现。您只保留最后的N个值,将一个索引保留在缓冲区中以告诉您最旧的值在哪里,在每一步中都用最新的值覆盖当前的最旧值,然后每次循环索引。

您会将要处理的收集数据保留在磁盘上。

根据您的环境,这可能是您最好获得经验丰富的帮助的地方之一。在大学里,您在计算机科学系的公告板上贴了笔记,为学生提供几个小时的工资(甚至学生咨询费),以帮助您处理数据。或者,您可以提供本科生研究机会积分。或者其他的东西。


6
循环缓冲区确实确实是我想要的!现在,我已经安装了boost C ++库并包含boost / circular_buffer.hpp,并且可以按预期工作。谢谢
@John

2
只有非常短的FIR滤波器以直接形式在软件中实现,而SMA几乎从来没有。
史蒂夫·考克斯

@SteveCox:您使用的窗口边缘公式对于整数和定点滤波器非常有效,但是对于浮点运算(其中运算不可交换)而言是不正确的。
Ben Voigt

@BenVoigt我想您想回应我的其他评论,但是是的,该表格围绕量化​​引入了一个极限周期,这可能非常棘手。值得庆幸的是,这个特定的极限周期恰好是稳定的。
史蒂夫·考克斯

您实际上并不需要为使用uu增加循环缓冲区。您将使用比所需更多的内存。
GameDeveloper

13

每个问题都可以通过添加额外的间接级别来解决。这样吧。

您不能在C ++中删除数组的一部分。但是您可以创建一个仅包含要保留的数据的新数组,然后删除旧的数组。因此,您可以构建一个数据结构,该结构允许您从前端“删除”不需要的元素。它实际要做的是创建一个新数组,并将未删除的元素复制到新数组,然后删除旧数组。

或者,您可以使用std::deque,它已经可以有效地做到这一点。deque或“双端队列”是一种数据结构,适用于您从一端删除元素而向另一端添加元素的情况。


30
可以通过添加额外的间接级别来解决每个问题 ...除了许多间接级别。
YSC

17
@YSC:和拼写:)
莫妮卡(Monica)

1
对于这个特殊的问题std::deque,要走的路
davidbak

7
@davidbak-什么?无需不断分配和释放内存。在初始化时分配一次的固定大小的循环缓冲区更适合此问题。
David Hammen

2
@DavidHammen:也许,但是1)标准库的工具包中没有“固定大小的循环缓冲区”。2)如果您确实需要这种优化,可以通过做一些分配器的工作,以最大程度地减少重新分配deque。也就是说,根据要求存储和重新使用分配。因此,deque似乎完全可以解决该问题。
Nicol Bolas

4

您所获得的FIR和SMA答案对您而言很不错,但是我想借此机会推动更通用的方法。

您这里拥有的是数据:无需分三步来构造程序(获取数据,计算,输出结果),而这需要一次将所有数据加载到内存中,而是可以将其构造为管道

管道从流开始,对其进行转换,然后将其推入接收器。

在您的情况下,管道如下所示:

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

流并不总是适用的(当您需要随机访问数据时它们将不起作用),但是当它们出现时,它们会摇摆:通过对非常少量的内存进行操作,您可以将其全部保留在CPU缓存中。


另外请注意:您的问题似乎是“令人尴尬的并行”,您可能希望将大文件拆分为多个块(请注意,对于5个窗口进行处理,每个边界需要4个公共元素)然后并行处理这些块。

如果CPU是瓶颈(而不是I / O),则可以通过在将文件以大致相等的数量分割之后为每个内核启动一个进程来加快处理速度。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.