std :: управление памятью строки - PullRequest
5 голосов
/ 16 апреля 2011

У меня проблема с управлением памятью с помощью std :: string.

У меня есть приложение - многопоточный сервер с отсоединенными потоками (мне нужно присоединиться к ним, они выполнят работу и выйти), и я обнаружил, чточерез некоторое время использование памяти становится довольно высоким.Я начал экспериментировать, где проблема, и я создал тестовую программу, которая демонстрирует проблему

#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);
}

после первого «---», использование памяти составляет 364 КБ, после второго - 19 МБ, после третьего33,5 МБ.также есть 1 странная вещь - каждый прогон показывает разное использование памяти, но в основном последнее использование памяти примерно на 50% больше, чем после второго «---».

я ожидал, что если класс TEST(tst) удаляется, тогда строка освобождает свою память - я обнаружил, что потоки этого не сделают - поэтому я создаю новый tst, запускаю его и затем удаляю.

в моей программе это вызываетбольшая проблема, потому что я использую там несколько строк в каждом потоке, и через некоторое время использование памяти превышает гигабайт; - (

есть ли опция, как «очистить» память после строки?

я пробовал TTT = "" или TTT.clear () без каких-либо изменений.

... мне нужно использовать потоки - он будет работать на машине с несколькими процессорами, где потоки являются единственной возможностьюиспользуй его "на полную мощность" (как я знаю)

Ответы [ 5 ]

6 голосов
/ 16 апреля 2011

Память, используемая строкой в ​​calc(), будет освобождена после выхода из функции calc. Удаление каждого tst объекта не имеет к этому никакого отношения, поскольку в нем нет членов класса.

Я думаю, вы видите, что потенциально работает 200 потоков. Поскольку вы отключаете все потоки и никогда не присоединяетесь к ним, у вас нет гарантии, что первые 100 действительно закончились до того, как вы начнете следующие 100.

Кроме того, поскольку вы расширяете эти строки снова и снова, добавляя и в то же время выделяя память в других потоках, я думаю, что ваша фрагментация кучи действительно действительно действительно плохо , Скажем, что один поток только что освободил память для 256-байтовой строки. Другие потоки работали впереди, и ни одному из них больше не нужна строка из 256 байтов. Это пространство теперь просто потрачено впустую, но все еще выделено для программы.

Некоторые параметры, которые могут помочь:

  • Используйте .reserve(your_largest_expected_string_size) перед размещением данных в вашей строке. Это поможет избежать проблемы фрагментации, поскольку почти все строки будут одинакового размера.
  • Настроенный класс распределителя строк. Вы можете написать свой собственный.
  • Вы можете заменить свой распределитель кучи чем-то вроде jemalloc или tcmalloc.
  • Для серверного приложения, которое обрабатывает клиентов, которые приходят и уходят, вы можете добиться высокой производительности, используя пул памяти для каждого клиента. Выделите большой пул памяти в одном блоке. Сделайте все выделения, используя пул (через параметры распределителя STL), и когда клиент выйдет, освободите весь пул.
5 голосов
/ 16 апреля 2011

Я подозреваю, что вы видите проблему фрагментации памяти, а не утечки памяти, как таковой.Поскольку вы не ожидаете завершения потоков, у вас есть целых 200 потоков, которые пытаются выделить память одновременно.Добавьте к этому стратегию выделения памяти по умолчанию для std::string, которая удваивает текущее распределение каждый раз, когда необходимо его увеличить, и вы, вероятно, довольно быстро пережевываете свое адресное пространство.

Одна очень простая вещь, которую вы можете сделатьЧтобы помочь смягчить проблему, нужно предварительно выделить память для строк с помощью reserve().В этом случае ваши строки имеют длину (31 * 100 000) + 1 байт, поэтому вы можете изменить calc() следующим образом:

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 ";
}

Это выделит память для строк один раз в одном смежном блоке.и избегайте большей части фрагментации, которой вы страдаете сейчас.Быстрая проверка этого изменения в Valgrind показывает, что исходный код выделяет около 1,5 ГБ всего за время жизни процесса;но модифицированная версия выделяет около 620 МБ.

Стоит также отметить, что Valgrind не обнаружил утечки памяти.Это всегда хорошая идея, если вы думаете, что у вас есть проблемы с памятью в вашей программе: Valgrind home .

2 голосов
/ 16 апреля 2011

В зависимости от реализации new и delete (или free () и malloc ()) ваша память может не быть возвращена ОС после удаления / освобождения. Распределитель памяти не требуется для этого. Память все еще может быть освобождена последующим новым, или после дефрагментации внутренней памяти или сбора мусора использование памяти может снова уменьшиться.

2 голосов
/ 16 апреля 2011

Строка должна быть освобождена сразу после выхода из функции calc, потому что это локальная переменная.

Что вы используете для отслеживания использования памяти? Возможно, вы видите фрагментацию памяти, а не увеличение использования памяти. Вот инструмент, который может показать, фрагментирована ли ваша память: http://hashpling.org/asm/

Я не знаком с pthreads, поэтому не могу сказать, может ли там быть проблема с потоками или что-то еще, связанное с отсутствием освобождения API.

0 голосов
/ 23 апреля 2011

проблема решена путем помещения «потоковой подпрограммы» в другую область видимости - if (1) {..}, поскольку, как я обнаружил на нескольких форумах, потоки не вызывают деструкторы при выходе, тогда в этом случае (без добавления блока) каждый созданный потоквыделит память для 'int cnt' и никогда не будет освобожден

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

... я обнаружил еще несколько маленьких ошибок в моем проекте, но это была главная проблема

...