【问题标题】:allocate but don't construct a large array of C++ objects分配但不构造大量 C++ 对象
【发布时间】:2017-02-21 22:00:49
【问题描述】:

在某些情况下,我的 C++14 程序需要大约 1 亿个“块”complex<float>,这需要接近 1 GB 的 RAM。我们可以放心地假设所需的内存将可用。

但是分配一个新的std::vector 真的很慢,因为复杂的构造函数调用了1亿次。在我的机器上,代码需要大约一秒钟的时间来初始化数组。

相比之下,调用calloc(),将分配的内存初始化为零,效果基本相同,运行时间非常短。

事实证明,我们甚至不需要这个初始化,因为大数组中的复合体稍后会从外部源填充。因此,我正在考虑将那个“块”中的对象的构造推迟到以后,然后直接从外部数据源构造它们。

所以我的问题是,是否有一种安全的惯用且有效的 C++ 方法来做到这一点,也许在此过程中使用 C++ 移动语义?如果不是,我们决定简单地malloc 内存块,那么我们可以简单地将内存块reinterpret_cast 转换为complex<float> 的普通旧C 数组吗?

感谢您的帮助

让-丹尼斯·梅斯

【问题讨论】:

  • 使用std::vector<>,但使用reserve()成员函数,而不是resize()或大小的构造函数。
  • 用优化编译真的这么慢吗?我不敢相信。
  • 为什么要在这种情况下使用矢量?您不能将内存附加/分离到向量,但您可以使用指针而不是迭代器来使用所有标准算法。
  • 我不想想要使用vector。我尝试过 vector 和原始的malloc,并得到了巨大的性能差距,这对我来说实际上很重要。我基本上是在寻找性能、清晰度、安全性和类似质量标准之间的最佳点。
  • 附带说明,calloc 经常可以cheat and not really allocate memory, or wait before really handing out the memory to you,费用稍后会出现。不要依赖它在呼叫点的明显良好表现。

标签: performance optimization constructor c++14 large-data


【解决方案1】:

所以你的问题是你是否可以使用malloc()。多年前,我在我的旧 C++ 编译器中使用了相同的方法,并且成功了。但最后我不得不打电话给free() 而不是delete[]。我认为这是特定于实现的,所以你应该在你的编译器上尝试一下。

【讨论】:

    【解决方案2】:

    我强烈建议坚持使用 c++ 并避免自己手动管理内存。

    标准库应该足够了。例如

    std::vector< complex > my_vector;
    
    // Reserve the necessary space without constructing anything
    my_vector.reserve( 100'000'000); 
    
    // construct the elements when needed
    populate( my_vector ); 
    

    【讨论】:

    • 我同意你的“强烈建议”。我会尝试使用vector::reserve()
    【解决方案3】:

    如果您将complex&lt;float&gt; 类的默认构造函数定义为空,从而使成员变量未初始化,那么鉴于编译器优化已打开,这两个操作之间应该没有任何实际区别。

    假设complex 类的定义如下。

    template <typename T>
    struct complex
    {
      complex() {}; // Empty constructor does nothing
      T a, b;
    };
    

    在启用 x86-64 gcc 6.2 和 -O2 的情况下使用 vector 初始化生成的程序集是:

    std::vector<complex<float>> v(100);
    
        mov     edi, 800
        call    operator new(unsigned long)
        mov     rdi, rax
        call    operator delete(void*)
    

    手动调用mallocfree生成的程序集是:

    auto v = malloc(100 * sizeof(complex<float>));
    free(v);
    
        mov     edi, 800
        call    malloc
        mov     QWORD PTR [rsp+8], rax
        mov     rdi, QWORD PTR [rsp+8]
        call    free
    

    如您所见,vector 实现不再为每个元素调用complex&lt;float&gt; 的构造函数。 vector 的用法更正确、更易读,并且还利用了 RAII 有助于防止内存泄漏。

    【讨论】:

    • 这是一个很好的观点。当然,这个方案需要定义一个自定义的复杂类,而不是使用std::complex
    • 在我的环境中(mac上的clang),复杂的默认构造函数是这样定义的:complex(const value_type&amp; __re = value_type(), const value_type&amp; __im = value_type()) : __re_(__re), __im_(__im) {}
    • 我明白了,所以看起来std::complex 总是初始化它的实值和虚值,如果你在std::vector 中同时初始化它们,这总是会让你变慢。
    • 是的,目前,没有任何级别的 Clang 优化可以优化这一点
    • 实际上,有趣的是,使用 x86-64 clang 3.9.0 进行测试会为 std::vector&lt;std::complex&lt;float&gt; &gt; v(100); 生成优化的汇编代码。然而,x86-64 gcc 6.2 似乎会为 std::complex 的每个元素调用构造函数。 Try it online here
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-23
    • 2012-06-04
    • 1970-01-01
    • 1970-01-01
    • 2016-02-05
    • 2022-12-11
    • 2018-10-07
    相关资源
    最近更新 更多