为什么运行空函数的std::thread会占用大量内存?

7
我写了一个简单的程序,应该运行两个线程,对小数组(约4096字节)进行排序并写入输出文件。输入数据包含在一个大文件中(约4GB)。计算机内存为128MB。我发现仅运行空主函数就会使用14MB内存。如果运行带有空函数的std :: thread,则每个线程开始使用约8MB。但是,如果我只进行一次动态内存分配,程序启动时每个线程将使用约64MB。我不明白可能会花费这么多内存。如何控制此大小?如何分配动态内存以最小化某些系统默认分配?
  • System: Ubuntu 14.04.3
  • Compiler: gcc 4.8.4
  • Compiler option:'-std=c++11 -O3 -pthread'

  • This is a code example

    void dummy(void)
    {
        std::vector<unsigned int> g(1);
        int i = 0;
        while( i<500000000)
        {
            ++i;
        }
    }
    
    int main(void)
    {
        std::thread t1(&dummy);
        std::thread t2(&dummy);
        std::thread t3(&dummy);
        t1.join();
        t2.join();
        t3.join();
        return 0;
    }
    

2
你是如何确定程序使用了多少内存的? - Pete Becker
1
我不信任你的措施。你是如何发现内存占用的? - SergeyA
3
即使线程函数什么也不做,操作系统也可能分配一个堆栈。8 MB 的情况并不超出实际情况。而 64 MB 的情况......有点奇怪。 - user4581301
在Ubuntu控制台中输入ulimit -s,你会得到什么? - Rama
4
我可以看到分配的地址空间,但为什么要关注实际的页面?哦,等等,楼主在读取VmSize吗?这似乎是一个奇怪的事情来衡量。如果你的进程请求了一千兆字节的地址空间却从不使用它,你为什么要关心呢?未使用的虚拟内存空间应该很便宜,除非你的硬件不支持未被使用的虚拟内存页面不被物理分配或其他类似的情况。 - Yakk - Adam Nevraumont
显示剩余3条评论
2个回答

8
每个线程都有自己的堆栈。在Linux上,默认堆栈大小为8 MB。当您第一次开始分配内存时,堆内存分配器可能实际上会一次性保留一大块。这可能可以解释您看到的每个线程64 MB的情况。
话虽如此,当我说“已分配”时,这并不意味着此内存确实被使用。分配发生在进程的虚拟内存空间中。当您运行ps时,在列VSZ下,或者当您运行top时,在列VIRT下,您所看到的就是这种情况。但是Linux知道您可能不会实际使用那些已分配的内存。因此,虽然您已经分配了一块虚拟内存,但Linux并不会为其分配任何物理内存,直到该进程实际开始向该内存写入数据。进程实际使用的真实物理内存量在ps中看到的是RSS,在top中看到的是RES。Linux允许分配更多的虚拟内存而超过了总物理内存。
即使您没有用尽物理内存,如果您在32位系统上有很多线程,每个线程分配8 MB的虚拟内存,可能会用尽您进程的虚拟内存空间(大约为2 GB)。虽然C++的线程库不允许您更改堆栈的大小,但是C pthreads库允许您通过提供pthread_create()和使用pthread_attr_setstacksize()调整pthread_attr_t来进行更改。另请参见此stackoverflow问题

考虑到系统总共只有128 MB的RAM,你的进程很少会用完地址空间。Linux可能会先用完RAM。 - MSalters
@MSalters:Linux会过度分配内存,因此在耗尽RAM之前可能会耗尽地址空间。 - G. Sliepen
理论上可以,但只有大约80MB的可用RAM(20K页)和2048MB的地址空间(512K页),你需要有一个巨大的过度承诺。 - MSalters
Linux的过度承诺没有限制。比RAM分配更多的地址空间非常容易。例如,如果编写创建每个传入连接一个线程的网络守护程序,则如果您有80 MB的RAM,则只需要10个传入连接即可用于这些线程的堆栈。幸运的是,由于过度承诺,您的程序将继续正常工作。 - G. Sliepen

0

您在上面的评论中报告的ulimit -s值确实表明该线程仍在分配堆栈,即使它是一个空的主线程。在线程中执行的函数调用需要一个堆栈来传递返回地址,假设您使用的是x86。

@Karrek SB的想法是正确的。您正在使用的分配器可能会影响程序的堆大小。为了避免重复调用brk或sbrk,分配器通常会请求更大的初始内存块。当分配器首次初始化时,不合理地期望以MB为单位的值,特别是那些沿着典型页面边界对齐的值,如4、8、32、64等。

要控制分配多少内存,您的结果可能会有所不同。请查看您的分配器是否支持mallopt函数。通过一些试验和错误,您可能能够减少整体内存占用。否则,您可以始终实现自己的分配器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接