将大对象放在堆上的最佳方法是什么?

时间:2013-08-20 07:22:02

标签: c++ c++11 bigdata heap-memory

我正在开发一个需要从数据文件加载许多对象并将它们存储在内存中的项目。因为我被告知堆栈空间很少,并且堆上的数据量应该更多,所以我将所有内容放在堆上。但是,我的印象是我过度了一点。

我目前的设计如下:

class RoadMap
{
    unique_ptr<set<unique_ptr<Node>>> allNodes;

    void addNode(unique_ptr<Node> node)
    {
        this->allNodes->insert(std::move(node));
    }
}

int main()
{
    unique_ptr<RoadMap> map(new RoadMap());

    // open file etc.

    for (auto nodeData : nodesInFile)
    {
        map->addNode(unique_ptr<Node>(new Node(nodeData)));
    }
}

从我现在所理解的,这会产生很多开销,因为我认为我不需要涉及许多独特的指针。如果我理解正确,那么在“指针链”中只有一个唯一的指针屏障就足够了。但是,我不确定这样做的最佳做法是什么。

选项1

class RoadMap
{
    unique_ptr<set<Node>> allNodes;

    void addNode (Node node)
    {
        this->allNodes->insert(node);
    }
}

int main()
{
    RoadMap map;
    //open file etc.
    for (auto nodeData : nodesInFile)
    {
        map.addNode(Node(nodeData));
    }
}

这样做的好处在于,RoadMap类本身是唯一需要处理堆分配的类,并且在创建set时只执行一次。

选项2

class RoadMap
{
    set<Node> allNodes;

    void addNode (Node node)
    {
        this->allNodes.insert(node);
    }
}

int main()
{
    unique_ptr<RoadMap> map(new RoadMap());
    // open file etc.
    for (auto nodeData : nodesInFile)
    {
        map->addNode(Node(nodeData));
    }
}

这里唯一的指针只在main函数中,这意味着RoadMap类的用户需要知道这个对象可以变得非常大并且应该放在堆栈中。我不认为这是一个非常好的解决方案。

选项3

class RoadMap
{
    set<unique_ptr<Node>> allNodes;

    void addNode(unique_ptr<Node> node)
    {
        this->allNodes.insert(std::move(node));
    {
}

int main()
{
    RoadMap map;
    // open file etc.
    for (auto nodeData : nodesInFile)
    {
        map.addNode(unique_ptr<Node>(new Node(nodeData)));
    }
}

此解决方案使用许多唯一指针,这意味着在删除RoadMap时,需要调用许多析构函数和delete。此外,RoadMap调用者在添加节点时必须提供unique_ptr,这意味着他必须自己进行堆分配。


现在,我赞成选项1胜过其他选项。但是,我只是在相对较短的时间内编写了C ++,并且不确定我是否完全理解了内存管理背后的概念,这就是为什么我希望你(在)验证我的观点。假设选项1是最好的方法,我是否正确?对于这类事情,您是否还有其他最佳实践参考?

4 个答案:

答案 0 :(得分:5)

Node一个移动构造函数并移动赋值运算符(使集合上的操作便宜),然后使用选项1和2的混合。std::set已经是堆分配其内容所以你不需要担心在堆上分配RoadMap。请注意std::move内的额外addNode,以允许Node移动到集合中。

class RoadMap
{
    set<Node> allNodes;

    void addNode (Node node)
    {
        allNodes.emplace(std::move(node));
    }
};

int main()
{
    RoadMap map;
    // open file etc.
    for (const auto& nodeData : nodesInFile)
    {
        map.addNode(Node(nodeData));
    }
}

答案 1 :(得分:1)

他们每个人都彼此截然不同。

为简单起见,我建议选项2。但是在sort等某些操作中,它可能会更加性能密集,因为您将移动整个Node而不是指向它的指针。

我认为这不是问题,因为您正在使用set。您仍然可以通过在Node对象上使用移动语义来优化它。除此之外,您仍然每次添加使用1份。

我提到的上述问题可能是vector的问题。直接存储对象的另一个问题是缺少多态性。你不能存储Node的子类型,它们会被切片。

如果这是一个问题我会建议选项2.存储指针意味着移动它们更快,并且多态性可以工作。

我认为没有选择1或原始解决方案的理由。

P.S。您的代码中的this->是不必要的。

p.p.s正如DyP所指出的那样set仍然使用堆,这就是使选项2变好的原因。线索 - 基于堆栈的结构无法增长。 =&GT;我认为只有std::array存储在堆栈中。

答案 2 :(得分:1)

让我谈谈元问题:您不希望堆栈溢出,从而将您的数据结构放在堆上。这是正确的做法。但是要理解的重要一点是事情将被放到堆上。

每个局部变量都在堆栈上分配。如果您有动态大小的数据结构,那么它们会引用(最重要的)所有情况下的堆。 (我所知道的唯一例外是当您使用alloca()std::get_temporary_buffer()或类似内容保留堆栈上的内存时。特别是所有STL容器将它们的内存保留在堆上,并且几乎不使用任何用于局部变量或成员变量的堆栈内存(除了std::array,其大小在编译时是已知的)。

因此,如果要保存堆栈内存,将动态大小的数据结构包装到unique_ptrs中的效果很小,但它会为程序增加间接性,从而使代码复杂化,减慢执行速度并不必要地增加堆内存使用量。

以下是一个示例:在具有32位编译的Visual Studio 2010上,std::set将在堆栈上使用20个字节的内存,与模板类型参数和集合中包含的实际数字元素无关。 set元素的内存在堆上。

我相信,您现在可以自行决定是否将unique_ptrs用于您的目的。

答案 3 :(得分:0)

基本上,它还取决于您希望如何访问RoadMap实例中存储的Node实例。我假设您的Node实例将释放包装的笔记数据。

我会去调整版本2.