微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

C ++大双端队列-程序需要很长时间才能退出?

如何解决C ++大双端队列-程序需要很长时间才能退出?

请考虑以下C ++程序:

#include <deque>
#include <iostream>

using namespace std;

int main()
{
    deque<double> d(30000000);
    cout << "Done\n";
}

第一行中的内存分配只需要一秒钟,但是在打印Done之后,需要33秒(!)才能返回到终端。将元素数量减少到2000万可以将时间减少到22秒,因此很明显,元素数量是线性的。

我正在Windows 10上进行编译,GCC 10.2.0和Visual Studio 2019都发生了同样的事情。

这是怎么回事?我是否以一种不应该使用的方式使用deque

编辑:

#include <deque>
#include <iostream>

using namespace std;

void test_deque()
{
    deque<double> d(30000000);
    cout << "Function done\n";
}

int main()
{
    test_deque();
    cout << "Main done\n";
}

现在它打印Function done,然后有33秒的延迟。因此,我认为这与在函数退出时执行的析构函数有关。但是为什么破坏240 MB的内存需要这么长时间?

编辑2:在Ubuntu上使用GCC进行了尝试(第二个版本),只需一秒钟的时间即可运行!与某些在线C ++编译器相同。这是Windows特有的问题吗?

编辑3:使用vector只需花费一秒钟的时间。但是,使用list(和forward_list)时,我会得到类似的极长延迟。

编辑4:在发布(而不是调试)配置中使用MSVC进行编译也只需一秒钟的时间。我不确定GCC相当于什么,但是使用-O3(最大优化),执行时间仍为33秒。

解决方法

根本上,答案不是很有趣。您的程序是无操作的,因此编译器可以优化双端队列的构造。但这不是必须的。

但是首先,合法的理智实施可以执行以下任何操作:

  1. 对30000000个float元素进行分配,仅此而已。分配器可以:

    1. 以最懒惰的方式进行分配,基本上只做一些记账工作。
    2. 在内存中过度分配和分页,导致每页面大小操作30000000。
    3. 零初始化或模式初始化(​​例如0xdeadbeef)可帮助检测未初始化的内存使用情况,从而导致30000000次写入。
  2. 分配(包括上面的内容)并对内存进行零初始化或模式初始化。

  3. 对所有元素运行某种析构函数(例如,将内存清零)。

  4. 由于它是内置类型,因此请勿在任何元素上运行析构函数。

现在,以上所有选项都是可能的。并且由于您的程序是无操作的,因此合法的编译器可能会优化这些步骤中的任何步骤或没有步骤。您的系统分配器的功能可能有所不同,支持延迟分配,过量使用,自动清零等。因此最终结果是,您可能会根据操作系统,编译器版本,编译器标志,标准库等获得任何行为。 / p>

,

在调试模式下真的很慢。

MSDN

调试器创建的进程(也称为衍生进程)的行为与调试器未创建的进程稍有不同。

调试器创建的进程不使用标准的堆API,而是使用特殊的调试堆。您可以通过使用_NO_DEBUG_HEAP环境变量来强制生成的进程使用标准堆而不是调试堆。

,

std::deque以固定大小的块分配数据,该块大小随平台和类型而变化。对于double,可能是4KB。因此,分配30,000,000个双精度数据将占用2.4GB的内存,因此需要进行6,000个分配/取消分配。如果使用std::list,则将是30,000的分配/取消分配,并且要占用几GB的内存,这一切都变得非常缓慢。

根据您的硬件,这甚至可能导致内存碎片问题。而且,如果您不进行优化就运行会更慢。

还有隐私问题。解除分配可能正在清除数据,以确保您的程序不会向外部程序泄漏任何信息。

正如@orlp所提到的那样,因为您的程序是无操作的,所以可以完全优化整个分配/解除分配,这可以解释为什么有时会显着提高速度。

,

MSVC具有内置的探查器。我们可以运行它(按Alt-F2键),以查看CPU的大部分时间都花在了分别调用deque::resize()deque::_Tidy()函数的构造函数和析构函数中。

msvc profiler

如果进一步深入研究,我们会发现deque::emplace_back()会产生大量代码

#define _PUSH_BACK_BEGIN                                                                                \
    if ((_Myoff() + _Mysize()) % _DEQUESIZ == 0 && _Mapsize() <= (_Mysize() + _DEQUESIZ) / _DEQUESIZ) { \
        _Growmap(1);                                                                                    \
    }                                                                                                   \
    _Myoff() &= _Mapsize() * _DEQUESIZ - 1;                                                             \
    size_type _Newoff = _Myoff() + _Mysize();                                                           \
    size_type _Block  = _Getblock(_Newoff);                                                             \
    if (_Map()[_Block] == nullptr) {                                                                    \
        _Map()[_Block] = _Getal().allocate(_DEQUESIZ);                                                  \
    }

#define _PUSH_BACK_END ++_Mysize()

    template <class... _Valty>
    decltype(auto) emplace_back(_Valty&&... _Val) {
        _Orphan_all();
        _PUSH_BACK_BEGIN;
        _Alty_traits::construct(
            _Getal(),_Unfancy(_Map()[_Block] + _Newoff % _DEQUESIZ),_STD forward<_Valty>(_Val)...);
        _PUSH_BACK_END;

#if _HAS_CXX17
        return back();
#endif // _HAS_CXX17
    }

反汇编视图:

    template <class... _Valty>
    decltype(auto) emplace_back(_Valty&&... _Val) {
00007FF674A238E0  mov         qword ptr [rsp+8],rcx  
00007FF674A238E5  push        rbp  
00007FF674A238E6  push        rdi  
00007FF674A238E7  sub         rsp,138h  
00007FF674A238EE  lea         rbp,[rsp+20h]  
00007FF674A238F3  mov         rdi,rsp  
00007FF674A238F6  mov         ecx,4Eh  
00007FF674A238FB  mov         eax,0CCCCCCCCh  
00007FF674A23900  rep stos    dword ptr [rdi]  
00007FF674A23902  mov         rcx,qword ptr [rsp+158h]  
00007FF674A2390A  lea         rcx,[__0657B1E2_deque (07FF674A3E02Fh)]  
00007FF674A23911  call        __CheckForDebuggerJustMyCode (07FF674A21159h)  
        _Orphan_all();
00007FF674A23916  mov         rcx,qword ptr [this]  
00007FF674A2391D  call        std::deque<double,std::allocator<double> >::_Orphan_all (07FF674A217FDh)  
        _PUSH_BACK_BEGIN;
00007FF674A23922  mov         rcx,qword ptr [this]  
00007FF674A23929  call        std::deque<double,std::allocator<double> >::_Myoff (07FF674A2139Dh)  
00007FF674A2392E  mov         qword ptr [rbp+0F8h],rax  
00007FF674A23935  mov         rcx,qword ptr [this]  
00007FF674A2393C  call        std::deque<double,std::allocator<double> >::_Mysize (07FF674A211B8h)  
00007FF674A23941  mov         rcx,qword ptr [rbp+0F8h]  
00007FF674A23948  mov         rcx,qword ptr [rcx]  
00007FF674A2394B  add         rcx,qword ptr [rax]  
00007FF674A2394E  mov         rax,rcx  
00007FF674A23951  xor         edx,edx  
00007FF674A23953  mov         ecx,2  
00007FF674A23958  div         rax,rcx  
00007FF674A2395B  mov         rax,rdx  
00007FF674A2395E  test        rax,rax  
00007FF674A23961  jne         std::deque<double,std::allocator<double> >::emplace_back<>+0D0h (07FF674A239B0h)  
00007FF674A23963  mov         rcx,qword ptr [this]  
00007FF674A2396A  call        std::deque<double,std::allocator<double> >::_Mapsize (07FF674A214BFh)  
00007FF674A2396F  mov         qword ptr [rbp+0F8h],rax  
00007FF674A23976  mov         rcx,qword ptr [this]  
00007FF674A2397D  call        std::deque<double,std::allocator<double> >::_Mysize (07FF674A211B8h)  
00007FF674A23982  mov         rax,qword ptr [rax]  
00007FF674A23985  add         rax,2  
00007FF674A23989  xor         edx,edx  
00007FF674A2398B  mov         ecx,2  
00007FF674A23990  div         rax,rcx  
00007FF674A23993  mov         rcx,qword ptr [rbp+0F8h]  
00007FF674A2399A  cmp         qword ptr [rcx],rax  
00007FF674A2399D  ja          std::deque<double,std::allocator<double> >::emplace_back<>+0D0h (07FF674A239B0h)  
00007FF674A2399F  mov         edx,1  
00007FF674A239A4  mov         rcx,qword ptr [this]  
00007FF674A239AB  call        std::deque<double,std::allocator<double> >::_Growmap (07FF674A21640h)  
00007FF674A239B0  mov         rcx,qword ptr [this]  
00007FF674A239B7  call        std::deque<double,std::allocator<double> >::_Mapsize (07FF674A214BFh)  
00007FF674A239BC  mov         rax,qword ptr [rax]  
00007FF674A239BF  lea         rax,[rax+rax-1]  
00007FF674A239C4  mov         qword ptr [rbp+0F8h],rax  
00007FF674A239CB  mov         rcx,qword ptr [this]  
00007FF674A239D2  call        std::deque<double,std::allocator<double> >::_Myoff (07FF674A2139Dh)  
00007FF674A239D7  mov         qword ptr [rbp+100h],rax  
00007FF674A239DE  mov         rax,qword ptr [rbp+100h]  
00007FF674A239E5  mov         rax,qword ptr [rax]  
00007FF674A239E8  mov         qword ptr [rbp+108h],rax  
00007FF674A239EF  mov         rax,qword ptr [rbp+0F8h]  
00007FF674A239F6  mov         rcx,qword ptr [rbp+108h]  
00007FF674A239FD  and         rcx,rax  
00007FF674A23A00  mov         rax,rcx  
00007FF674A23A03  mov         rcx,qword ptr [rbp+100h]  
00007FF674A23A0A  mov         qword ptr [rcx],rax  
00007FF674A23A0D  mov         rcx,qword ptr [this]  
00007FF674A23A14  call        std::deque<double,std::allocator<double> >::_Myoff (07FF674A2139Dh)  
00007FF674A23A19  mov         qword ptr [rbp+0F8h],rax  
00007FF674A23A20  mov         rcx,qword ptr [this]  
00007FF674A23A27  call        std::deque<double,std::allocator<double> >::_Mysize (07FF674A211B8h)  
00007FF674A23A2C  mov         rcx,qword ptr [rbp+0F8h]  
00007FF674A23A33  mov         rcx,qword ptr [rcx]  
00007FF674A23A36  add         rcx,qword ptr [rax]  
00007FF674A23A39  mov         rax,rcx  
00007FF674A23A3C  mov         qword ptr [_Newoff],rax  
00007FF674A23A40  mov         rdx,qword ptr [_Newoff]  
00007FF674A23A44  mov         rcx,qword ptr [this]  
00007FF674A23A4B  call        std::deque<double,std::allocator<double> >::_Getblock (07FF674A21334h)  
00007FF674A23A50  mov         qword ptr [_Block],rax  
00007FF674A23A54  mov         rcx,qword ptr [this]  
00007FF674A23A5B  call        std::deque<double,std::allocator<double> >::_Map (07FF674A21753h)  
00007FF674A23A60  mov         rax,qword ptr [rax]  
00007FF674A23A63  mov         rcx,qword ptr [_Block]  
00007FF674A23A67  cmp         qword ptr [rax+rcx*8],0  
00007FF674A23A6C  jne         std::deque<double,std::allocator<double> >::emplace_back<>+1D7h (07FF674A23AB7h)  
00007FF674A23A6E  mov         rcx,qword ptr [this]  
00007FF674A23A75  call        std::deque<double,std::allocator<double> >::_Getal (07FF674A216CCh)  
00007FF674A23A7A  mov         qword ptr [rbp+0F8h],rax  
00007FF674A23A81  mov         edx,2  
00007FF674A23A86  mov         rcx,qword ptr [rbp+0F8h]  
00007FF674A23A8D  call        std::allocator<double>::allocate (07FF674A216C7h)  
00007FF674A23A92  mov         qword ptr [rbp+100h],rax  
00007FF674A23A99  mov         rcx,qword ptr [this]  
00007FF674A23AA0  call        std::deque<double,std::allocator<double> >::_Map (07FF674A21753h)  
00007FF674A23AA5  mov         rax,qword ptr [rax]  
00007FF674A23AA8  mov         rcx,qword ptr [_Block]  
00007FF674A23AAC  mov         rdx,qword ptr [rbp+100h]  
00007FF674A23AB3  mov         qword ptr [rax+rcx*8],rdx  
        _Alty_traits::construct(
00007FF674A23AB7  mov         rcx,qword ptr [this]  
00007FF674A23ABE  call        std::deque<double,std::allocator<double> >::_Map (07FF674A21753h)  
00007FF674A23AC3  mov         rax,qword ptr [rax]  
00007FF674A23AC6  mov         qword ptr [rbp+0F8h],rax  
00007FF674A23ACD  xor         edx,edx  
00007FF674A23ACF  mov         rax,qword ptr [_Newoff]  
00007FF674A23AD3  mov         ecx,2  
00007FF674A23AD8  div         rax,rcx  
00007FF674A23ADB  mov         rax,rdx  
00007FF674A23ADE  mov         rcx,qword ptr [_Block]  
00007FF674A23AE2  mov         rdx,qword ptr [rbp+0F8h]  
00007FF674A23AE9  mov         rcx,qword ptr [rdx+rcx*8]  
00007FF674A23AED  lea         rax,[rcx+rax*8]  
00007FF674A23AF1  mov         rcx,rax  
00007FF674A23AF4  call        std::_Unfancy<double> (07FF674A214A6h)  
00007FF674A23AF9  mov         qword ptr [rbp+100h],rax  
00007FF674A23B00  mov         rcx,qword ptr [this]  
00007FF674A23B07  call        std::deque<double,std::allocator<double> >::_Getal (07FF674A216CCh)  
00007FF674A23B0C  mov         qword ptr [rbp+108h],rax  
00007FF674A23B13  mov         rdx,qword ptr [rbp+100h]  
00007FF674A23B1A  mov         rcx,qword ptr [rbp+108h]  
00007FF674A23B21  call        std::_Default_allocator_traits<std::allocator<double> >::construct<double> (07FF674A211E5h)  
            _Getal(),_STD forward<_Valty>(_Val)...);
        _PUSH_BACK_END;
00007FF674A23B26  mov         rcx,qword ptr [this]  
00007FF674A23B2D  call        std::deque<double,std::allocator<double> >::_Mysize (07FF674A211B8h)  
00007FF674A23B32  mov         qword ptr [rbp+0F8h],rax  
00007FF674A23B39  mov         rax,qword ptr [rbp+0F8h]  
00007FF674A23B40  mov         rax,qword ptr [rax]  
00007FF674A23B43  inc         rax  
00007FF674A23B46  mov         rcx,qword ptr [rbp+0F8h]  
00007FF674A23B4D  mov         qword ptr [rcx],rax  

#if _HAS_CXX17
        return back();
00007FF674A23B50  mov         rcx,qword ptr [this]  
00007FF674A23B57  call        std::deque<double,std::allocator<double> >::back (07FF674A2127Bh)  
#endif // _HAS_CXX17
    }
00007FF674A23B5C  lea         rsp,[rbp+118h]  
00007FF674A23B63  pop         rdi  
00007FF674A23B64  pop         rbp  
00007FF674A23B65  ret

显然std::deque不会预分配元素,而是使用循环将它们一个一个地添加。所以难怪它很慢。

您可以通过启用一些优化(例如/Ob1)和减少运行时检查(例如删除/RTC1)来加快Debug的构建速度。

但是实际上,std::deque从性能的角度来看只是一个可怕的结构(微小矢量的矢量-根本不支持缓存)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。