【问题标题】:Improve storage capacity / performance of std::vector提高 std::vector 的存储容量/性能
【发布时间】:2016-02-26 02:00:02
【问题描述】:

我正在构建一个建模软件我有几个关于如何获得最佳性能的问题?

1) 我应该使用 std::vector<class> 还是 std::vector<class*> ? 我的课相当复杂/很大,我认为使用第二个选项更好,因为 std::vector 尝试连续分配内存并且可能没有连续的内存块来存储一百万class,但是当我只需存储指针,class 不必连续存储,只需存储指针,并且计算机可能有空间来执行此操作。这个推理正确吗?

2) 正如我所说,我将拥有数百万个class,(为了进行适当的模拟,我需要> 十亿个class)在这里使用继承是否明智? 对于我的模拟,有多种不同的类型继承自同一个基类,

class A - class B 
        - class C
        - class D 

我是否应该避免继承,因为我一直听说使用继承会降低性能?

3) 另外,我如何将所有这些不同的类存储在 std::vector 中? std::vector<base_class * >std::vector<base_class> 可以存储类 B、类 C、类 D,它们都继承自基类吗?

4) 在之前版本的程序中,我通过让不同的进程处理 std::vector 的不同部分来使用多线程,有没有更好的线程处理方法?

5) 我应该使用智能指针吗?既然我有这么多对象,它们会降低性能吗?

我正处于计划阶段,非常感谢任何帮助。

【问题讨论】:

  • 不要回避抽象。测量。
  • 您可以考虑std::deque,它也具有恒定的访问时间,但将数据分配在块中,然后永远不会重新分配。
  • @user3159253 我将研究 std::deque ,但快速的问题是否允许我像 std::vector 一样快速地遍历项目?
  • 如果你希望有一个混合类型的容器,那么指针是不可避免的,可能是unique_ptr。完全独立地并行处理不同的数据块(想想分片)是一个聪明的主意。关于双端队列的性能:是的,访问时间是 O(1),只比向量的稍差(它必须首先计算所需块的位置)。是的,进行性能测试。
  • 继承的性能损失可能完全不同。如果你经常使用virtual 方法,那么,是的,与非虚拟情况相比,它会降低性能。另一方面,内联可以在更高的优化级别上提供很大帮助。但是,再次,通过测试检查每个想法,取决于编译器和目标平台,结果可能完全不同

标签: c++ multithreading c++11 inheritance vector


【解决方案1】:

我每天都在专业环境中处理这样的问题(我是一名 C++ 程序员,处理大数据集)。因此,我在这里要说的既是个人建议,也是答案。我不会在简单的部分上全力以赴:

1 - 是的存储指针,它将比重新分配和移动时间快得多,而不是完整的类对象。

2 - 是的,如果对象具有相关信息,则使用继承,我想在这种情况下,它们很可能会像您考虑的那样做。如果没有,你为什么要把它们放在一起?

3 - 使用指向基类(父对象)的智能指针将它们全部存储,因此您可以添加单个虚拟“get_type”函数来返回和枚举,并在需要时转换为子类 /em>。如果您不经常需要子数据,这将节省提供多个虚拟方法的开销。

4 - 有争议,但对较大数组的单独部分进行线程处理是更简单的方法(当您处理 巨大 复杂的数据时,越简单越好。

每个人都知道,调试的难度是编写程序的两倍。因此,如果您在编写它时尽可能聪明,您将如何调试它? ~ 布赖恩·克尼汉

5 - 使用智能指针(As explained in this question)会有一些小的惩罚,但在我看来,与易用性和复杂性损失相比,惩罚(尤其是使用 unique_ptr)是如此之小,这绝对是值得

把它们放在一起:

class Abstract_Parent;
std::vector<std::unique_ptr<Abstract_Parent>> Data;
enum ChildType {Child_1 = 0, Child_2 = 1};

class Abstract_Parent
{
    public:
    virtual ChildType GetType() = 0;
}   
class Child_One
{
    public:
    virtual ChildType GetType() { return Child_1; }
}   
class Child_Two
{
    public:
    virtual ChildType GetType() { return Child_2; }
}   
void Some_Function()
{
    //this is how to insert a child-object
    std::unique_ptr<Abstract_Parent> Push_me_Back(new Child_One());
    Data.Push_Back(std::move(Push_me_Back));

    if(Data[0]->GetType() == Child_1) 
    {
        Child_1 *Temp_Ptr = dynamic_cast<Child_One*> Data[0];
        Temp_Ptr->Do_Something_Specific();
    }
}

【讨论】:

  • 我想使用指针而不是对象的真正原因是多态性。如果您仅在矢量后面插入/删除对象,则移动和重新分配不会成为问题。
  • @NikolayKondratyev,我必须不同意,即使在向量的后面插入对象,你也会达到它的保留内存大小。当您为更大的内存分配内存时,数据将被移动。如果您插入数百万个元素,这将变得非常昂贵。其次,许多复杂算法需要对数据进行排序,在 std::sort 期间必须进行交换操作。如果数据大于指针,所有这些都会花费更多。
  • 当然,如果您在开始之前就知道对象的最大数量,则可以在一定程度上缓解这种情况。
  • @JohnBargman 谢谢你的回答。在编写处理许多对象的高性能、多线程代码时,是否有任何书籍/资源对您有所帮助?
  • @nnrales,我希望我能 - 但是我从行业工作中学到了我的大部分设计模式。但是,我听说过有关“Scott Meyers 的有效现代 C++”的好消息。我个人的建议是观看 c++ 创建者的一些 cpp-con 视频,以及阅读一些关于“现代 C++”的文章,例如 - msdn.microsoft.com/en-GB/library/hh279654.aspx
【解决方案2】:

1.) 这取决于您的用例。如果您想通过基类指针访问对象,您将使用指针。另一方面,您失去了连续内存和缓存代码和数据的局部性的优势。

2.) 如果您需要 10 亿个实例,那么每个对象的每个额外数据都会增加您的内存占用。例如,一个指向 8 字节的虚函数表 (vptr) 的附加指针将使您的内存需求增加 8 GB。在没有虚拟基类的情况下将每种类型存储在不同的向量中没有这种开销。

2b) 是的,如果您以性能为目标,您应该避免使用虚函数进行继承。如果使用不同的实现调用虚函数,指令缓存将被丢弃。至少你可以按类型对你的大向量进行排序,以尽量减少这个问题。

3.) 如果您使用具有虚函数的基类,则必须使用指针选项来防止切片。

4.) 需要更多信息,应在单独的问题中回答。

5.) 每次间接都会降低性能。

【讨论】:

  • 这很有启发性。谢谢
【解决方案3】:

1) 我应该使用std::vector&lt;class&gt; 还是std::vector&lt;class*&gt;

错误的二分法。还有其他几个选项:

  • boost::ptr_vector&lt;class&gt;
  • std::vector&lt;std::unique_ptr&lt;class&gt;&gt;
  • 可能更多。

我个人喜欢boost::ptr_vector&lt;class&gt;,因为它存储了一个拥有的指针(因此内存分配是自动完成的)。但是在访问成员时,它们会作为对对象的引用(而不是指针)返回。因此,与其他技术相比,将它们与标准算法一起使用会大大简化。

我的类相当复杂/大,我认为使用第二个选项更好,因为 std::vector 尝试连续分配内存,并且可能没有连续的内存块来存储一百万个类,

这里真正的问题是你是否可以预先计算你的向量的最大大小和reserve()所需的空间量。如果你能做到这一点(从而避免任何复制成本)std::vector&lt;class&gt; 将是最好的解决方案。

这是因为将对象放在连续存储中通常在速度方面具有显着优势(尤其是在扫描矢量时)。当您拥有庞大的数据集(尤其是在十亿范围内)时,不应低估执行此操作的能力。

但是当我只存储指针时,类不必连续存储,只需要存储指针,并且计算机可能有空间来执行此操作。这个推理正确吗?

通过使用指针,您还显着增加了应用程序所需的内存量,因为您需要存储对象和指向对象的指针。超过数十亿个对象,这可能是一笔巨大的成本。

2)正如我所说,我将拥有数百万个类,(为了进行适当的模拟,我需要 > 十亿个类)在这里使用继承是一件聪明的事情吗?

没有更多信息就无法说出来。

3) 另外,如何将所有这些不同的类存储在 std::vector 中? std::vector 或 std::vector 可以存储类 B 、类 C 、类 D 都继承自基类吗?

但如果你确实使用继承,你将不需要直接使用std::vector&lt;class&gt;。您将需要存储一个指向基类的指针。但这并不排除其他三种技术。

4) 在之前版本的程序中,我通过让不同的进程处理 std::vector 的不同部分来使用多线程,有没有更好的线程处理方法?

这似乎是一种合理的方法(假设范围不重叠并且是连续的)。不要创建多于可用内核的线程。

我应该使用智能指针吗?既然我有这么多对象,它们会降低性能吗?

在普通指针上使用unique_ptr 开销为零(假设您不使用自定义删除器)。实际生成的代码基本是等价的。

【讨论】:

    猜你喜欢
    • 2012-06-30
    • 2022-06-16
    • 2014-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多