Ajin Pradeep
Ajin Pradeep

Reputation: 97

Virtual memory size increases considerably with thread count

In the below C++ code example, each time the user presses the Enter key, a new thread will be created. The thread waits for 10 minutes and then exits. The thread has a std::string object with some data.

#include <iostream>
#include <thread>
#include <chrono>
#include <string>
#include <vector>

using namespace std;

// Function that each thread will execute
void threadFunction()
{
    std::string str;
    str = "abcdefghijklmnopqrstuvwxyz";

    std::cout << "--------------> Thread created" << std::endl;
    // Sleep for 10 minutes'
    this_thread::sleep_for(chrono::minutes(10));
    cout << "Thread finished sleeping for 10 minute!" << endl;
}

int main()
{
    char input;
    while (true)
    {
        cout << "Press Enter to create a new thread, or 'q' to quit: ";
        input = getchar(); // Wait for user input

        if (input == '\n')
        {                                     // If Enter key is pressed
            thread newThread(threadFunction); // Create a new thread
            newThread.detach();               // Detach the thread to let it run independently
        }
        else if (input == 'q')
        { // If 'q' is pressed, quit the program
            break;
        }
        // Ignore any other input
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
    }

    return 0;
}

For each thread, it is observed that the virtual memory is increasing considerably.

image

This is checked using the 'htop' command.

OS : Ububtu 20.04.6 LTS (Focal Fossa)
gcc: gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0

Note: This issue is not observed in Windows (mscv).

Can anyone explain why this happens, and how to address it? What is the root cause, and how can we fix it?

Upvotes: 0

Views: 148

Answers (1)

that other guy
that other guy

Reputation: 123630

You can see what's up in a debugger:

#0  __GI___mmap64 (addr=addr@entry=0x0, len=len@entry=134217728, prot=prot@entry=0, flags=flags@entry=34, fd=fd@entry=-1, 
    offset=offset@entry=0) at ../sysdeps/unix/sysv/linux/mmap64.c:50
#1  0x00007ffff7ab2895 in alloc_new_heap (size=135168, top_pad=<optimized out>, pagesize=4096, mmap_flags=mmap_flags@entry=0)
    at ./malloc/arena.c:518
#2  0x00007ffff7ab29e6 in new_heap (size=size@entry=2904, top_pad=<optimized out>) at ./malloc/arena.c:573
#3  0x00007ffff7ab2db2 in _int_new_arena (size=640) at ./malloc/arena.c:741
#4  arena_get2 (avoid_arena=<optimized out>, size=640) at ./malloc/arena.c:960
#5  arena_get2 (size=640, avoid_arena=<optimized out>) at ./malloc/arena.c:921
#6  0x00007ffff7ab5554 in tcache_init () at ./malloc/malloc.c:3220
#7  0x00007ffff7ab5cfe in tcache_init () at ./malloc/malloc.c:3217
#8  __GI___libc_malloc (bytes=31) at ./malloc/malloc.c:3282
#9  0x00007ffff7cb268c in operator new(unsigned long) () from /lib/x86_64-linux-gnu/libstdc++.so.6
#10 0x00007ffff7d4fe1e in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) () from /lib/x86_64-linux-gnu/libstdc++.so.6
#11 0x00007ffff7d50f0b in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long) () from /lib/x86_64-linux-gnu/libstdc++.so.6
#12 0x0000555555555254 in threadFunction() ()

glibc is allocating a new thread-local arena for each thread on its first allocation. If you remove the std::string, no allocation happens, so no arena is created and vspace usage only grows by the size of the stack.

The arena itself is relatively small, but glibc allocates a larger size than necessary for alignment reasons:

  /* A memory region aligned to a multiple of max_size is needed.
     No swap space needs to be reserved for the following large
     mapping (on Linux, this is the case for all non-writable mappings
     anyway). */

Upvotes: 5

Related Questions