5

I have problem with memory management with std::string.

I have application - multithread server with detached threads (i done need to join them, they will do the job and exit) and i found that after a while memory usage comes quite high. I've started to experiment where is the problem and i've created test program which demonstrating the problem

#include <iostream>

#include <string>
#include <pthread.h>

pthread_t           thread[100];

using namespace std;

class tst {
    public:
        tst() {
            //cout << "~ Create" << endl;
        }
        ~tst() {
            //cout << "~ Delete" << endl;
        }
        void calc() {
            string TTT;
            for (int ii=0; ii<100000; ii++) {
                TTT+="abcdenbsdmnbfsmdnfbmsndbfmsndb ";
            }
        }
};

void *testThread (void *arg) {
    int cnt=*(int *) arg;
    cout << cnt << " ";
    tst *TEST=new tst;
    TEST->calc();
    delete TEST;
    pthread_exit((void *)0);
}

int main (int argc, char * const argv[]) {

cout << "---------------------------------------------------" << endl;
sleep(5);

    for (int oo=0; oo<100; oo++) {
        pthread_create(&thread[oo], NULL, testThread, &oo);
        pthread_detach(thread[oo]);
    }
    cout << endl;
    cout << "---------------------------------------------------" << endl;

    sleep(5);

    for (int oo=0; oo<100; oo++) {
        pthread_create(&thread[oo], NULL, testThread, &oo);
        pthread_detach(thread[oo]);
    }
    cout << endl;
    cout << "---------------------------------------------------" << endl;

    sleep(5);
    exit(0);
}

after first "---" the memory usage is 364KB, after second its 19MB, after third is 33.5MB. there is 1 strange thing as well - each run showing different memory usage but mostly the last memory usage is about 50% more then after the second "---".

i've expected that if the class TEST (tst) is deleted then the string will release its memory as well - i found that threads will not do that - that's why i'm creating new tst, running it and then delete.

in my program this causing a big problem because i'm using there few strings in each thread and after a while the memory usage is over gig ;-(

is there any option how to 'empty' the memory after string?

i've tried TTT="" or TTT.clear() without any change.

...i need to use threads - it will be running on multicpu machine where threads is the only option to use it's 'full power' (as i know)

  • How are you measuring memory usage? – Doug T. Apr 15 '11 at 22:00
  • @Doug T.: Activity monitor - Mac OSX –  Apr 15 '11 at 22:01
  • @tominko, I wonder if the OS is just not very aggressive about reclaiming virtual memory it allocates. You might be allocated a certain amount but not actually using it. – Doug T. Apr 15 '11 at 22:06
  • Are you sure the memory usage is from the strings? Spinning up a thread takes a *fair* amount of memory (at least one page for page descriptors, another for the thread stack, etc.) – Jerry Coffin Apr 15 '11 at 22:07
  • 1
    With 100 threads, each creating a 3MB string, I'd expect higher memory usage: 300MB at least. Then factor in fragmented memory. – Paul Beckingham Apr 15 '11 at 22:13
  • @Jerry Coffin: it's from string. i've done another test without threads - just put two loops one after another with sleep between - after first loop the memory usage stayed on the 19Mb, after second loop it's not increased - it looks like the string using some 'cache' which causing problems in multithread environment –  Apr 15 '11 at 22:14
  • @Paul Beckingham: to me is std::string quite strange - even any other run showing different results.... i've just tried it on freebsd with simillar results –  Apr 15 '11 at 22:18
  • Look at this answer for some useful info: http://stackoverflow.com/questions/3770457/what-is-memory-fragmentation/3771062#3771062 – Zan Lynx Apr 15 '11 at 22:42
  • You can use another allocator for any std container. See http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt04ch11.html#allocator.ext for details on the ones available in libstdc++. You might want to use __gnu_cxx::malloc_allocator. – molbdnilo Apr 16 '11 at 07:42
  • 200 threads with 100000 allocations of 31 characters, plus 200 stacks of 1M, means an minimum memory usage of 792Mb, and potentially up to 1.54Gb with fragmentation. – Mooing Duck Jan 21 '15 at 01:32

5 Answers5

6

The memory used by the string in calc() will be released after the calc function exits. Deleting each tst object has nothing to do with it because there are no class members.

What I think you are seeing is that there are potentially 200 threads running. Since you detach all of the threads and never join them, you have no guarantee the first 100 have actually finished before you start the next 100.

Also, because you are extending these strings over and over again by appending while at the same time allocating memory in other threads, I imagine your heap fragmentation is really really really bad. Say that one thread has just released memory for a 256 byte string. The other threads have run on ahead and none of them need a 256 byte string anymore. That space is now just wasted, but is still allocated to the program.

Some options that could help:

  • Use .reserve(your_largest_expected_string_size) before placing data into your string. This will help avoid the fragmentation problem because almost all strings will be the same size.
  • A customized string allocator class. You could write your own.
  • You could replace your heap allocator with something like jemalloc or tcmalloc.
  • For a server application that handles clients that come and go you can get great performance using a per-client memory pool. Allocate a large memory pool in one block. Make all allocations use the pool (via the STL allocator parameters) and when the client exits free the entire pool.
Zan Lynx
  • 49,393
  • 7
  • 74
  • 125
  • there is no problem with the threads - if i remove the code from thread (leave it just empty) the memory is ok. The long string i've created is just for test - because if its there just short string the difference is too small. In real program there is (will be) sometimes 1000 requests per second, and sometimes none for few minutes but after a while the program allocate over gig of memory.. the threads management i have sorted there by global bool array which is set to false if thread finishes and then the next request can use its variable.. problem is that i cant clear the memory after string –  Apr 15 '11 at 22:26
  • 2
    @tominko: The threads make heap fragmentation much worse as I tried to illustrate in my answer. A per-thread custom memory allocator pool or a thread-aware global allocator would help your problem. – Zan Lynx Apr 15 '11 at 22:40
  • Amusingly, most of the problem can also be decreased by using fewer threads, and joining them. – Mooing Duck Jan 21 '15 at 01:26
5

I suspect that you're seeing an issue with memory fragmentation, rather than a memory leak, per se. Since you're not waiting for threads to exit, you've got as many as 200 threads all trying to allocate memory at the same time. Add to that the default memory allocation strategy for std::string, which doubles the current allocation each time it needs to grow, and you're probably chewing up your address space pretty quickly.

One very simple thing you can do to help mitigate the problem is to preallocate the memory for the strings by using reserve(). In this case your strings are (31 * 100,000) + 1 bytes long, so you could modify calc() as follows:

string TTT;

// We know how big the string will get, so pre-alloc memory for it to avoid the 
// inefficiencies of the default allocation strategy as the string grows.

TTT.reserve(3100001);

for (int ii=0; ii<100000; ii++) {
    TTT+="abcdenbsdmnbfsmdnfbmsndbfmsndb ";
}

This would allocate the memory for the strings once, in a single contiguous block, and avoid much of the fragmentation that you're suffering now. A quick test of this change under Valgrind shows that the original code does allocations of about 1.5 GB total over the lifetime of the process; but the modified version does allocations of about 620 MB total.

Also worth noting that Valgrind did not reveal any memory leaks. It's always a good idea to try it out if you think you have a memory problem in your program: Valgrind home.

Eric Melski
  • 15,052
  • 3
  • 32
  • 48
  • problem is not the allocation - even i dont know the max size of the string if i creating it - i have another version where i'm using 'my string' class which is char* with malloc and free which is working perfect - but i need to use std::string functions like find_first_not_of... (which will takes looong time to implement to my string class) - what i need is to clean after string somehow –  Apr 15 '11 at 22:32
  • @tominko: Do not forget that if you have a string in a char buffer the old C string functions still work. You could `#include ` and use `strcspn` to replace `find_first_not_of`. – Zan Lynx Apr 15 '11 at 22:38
  • @Zan Lynx: i think i have to get rid of std::string ;-( –  Apr 15 '11 at 22:42
  • @tominko no you don't, you have to fix your code to not do as much at once. – Mooing Duck Jan 21 '15 at 01:34
2

The string should be deallocating as soon as the calc function exits because it's a local variable.

What are you using to track your memory usage? You might be seeing memory fragmentation rather than increasing memory usage. Here's a tool that can demonstrate if your memory is getting fragmented: http://hashpling.org/asm/

I'm not familiar with pthreads, so I can't tell if there might be an issue there with the threads or something else related to the API not being deallocated.

CariElf
  • 204
  • 2
  • 6
  • i've expected that after going out of scope it will be cleared but not ;-( ...the program i cant use - i have no windows machine ;-( –  Apr 15 '11 at 22:16
2

Depending on the implementation of new and delete (or free() and malloc()) your memory might not given back to the OS after a delete/free. A memory allocator is not required to do so. The memory might still be reclaimed by a subsequent new, or after a internal memory defragmentation or garbage collection the memory usage might decrease again.

Gunther Piez
  • 28,058
  • 6
  • 62
  • 101
  • malloc, realloc and free working well and i see the difference straight-away in activity monitor, but the 'char* method' - i've done in another testing class string doesnt include functions i need which are in std::string –  Apr 15 '11 at 22:35
0

problem solved by putting 'threaded routine' in another scope - if (1) {..} because as i found on few forums - threads not calling destructors at exit then in this occasion (without added if block) every created thread will allocate memory for 'int cnt' and is never deallocated

void *testThread (void *arg) {
    if (1) {
        int cnt=*(int *) arg;
        cout << cnt << " ";
        tst *TEST=new tst;
        TEST->calc();
        delete TEST;
    }
    pthread_exit((void *)0);
}

...i found few more little bugs in my project but this was the main issue