【问题标题】:std::string memory managementstd::string 内存管理
【发布时间】:2011-08-06 15:57:24
【问题描述】:

我在使用 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);
}

在第一个“---”之后内存使用量为 364KB,第二个为 19MB,第三个为 33.5MB。 还有一件奇怪的事情 - 每次运行都显示不同的内存使用情况,但大多数情况下,最后一次内存使用情况比第二个“---”之后多 50%。

我预计如果类 TEST (tst) 被删除,那么字符串也会释放其内存 - 我发现线程不会这样做 - 这就是我创建新 tst、运行它然后删除的原因.

在我的程序中,这导致了一个大问题,因为我在每个线程中使用的字符串很少,并且一段时间后内存使用量超过了 gig ;-(

有什么选项可以在字符串之后“清空”内存吗?

我试过 TTT="" 或 TTT.clear() 没有任何改变。

...我需要使用线程 - 它将在多 CPU 机器上运行,其中线程是使用它的“全功率”的唯一选项(据我所知)

【问题讨论】:

  • 你如何测量内存使用情况?
  • @Doug T.:活动监视器 - Mac OSX
  • @tominko,我想知道操作系统是否在回收它分配的虚拟内存方面不是很积极。您可能被分配了一定数量但实际上并没有使用它。
  • 你确定内存使用来自字符串吗?启动一个线程需要 相当 的内存量(至少一个页面用于页面描述符,另一个用于线程堆栈等)
  • 有 100 个线程,每个线程创建一个 3MB 的字符串,我希望内存使用率更高:至少 300MB。然后考虑碎片化的内存。

标签: c++ string memory-management pthreads


【解决方案1】:

字符串应该在 calc 函数退出后立即解除分配,因为它是一个局部变量。

您使用什么来跟踪您的内存使用情况?您可能会看到内存碎片,而不是增加内存使用量。这是一个可以证明你的记忆是否碎片化的工具:http://hashpling.org/asm/

我不熟悉 pthread,所以我无法判断线程是否存在问题,或者与未释放 API 相关的其他问题。

【讨论】:

  • 我预计在超出范围后它将被清除但不会;-( ...我无法使用的程序 - 我没有 Windows 机器;-(
【解决方案2】:

calc()中的字符串使用的内存将在calc函数退出后释放。删除每个tst 对象与它无关,因为没有类成员。

我认为您看到的是可能有 200 个线程正在运行。由于您分离了所有线程并且从不加入它们,因此您无法保证前 100 个线程在开始下一个 100 个线程之前实际上已经完成。

另外,因为您通过附加来一遍又一遍地扩展这些字符串,同时在其他线程中分配内存,我想您的堆碎片真的真的 真的 不好。假设一个线程刚刚为一个 256 字节的字符串释放了内存。其他线程已经在前面运行,它们都不再需要 256 字节的字符串了。该空间现在只是浪费了,但仍分配给程序。

一些可能有帮助的选项:

  • 在将数据放入字符串之前使用.reserve(your_largest_expected_string_size)。这将有助于避免碎片问题,因为几乎所有字符串的大小都相同。
  • 自定义字符串分配器类。你可以自己写。
  • 您可以将堆分配器替换为 jemalloc 或 tcmalloc 之类的东西。
  • 对于处理来来往往的客户端的服务器应用程序,您可以使用每个客户端的内存池获得出色的性能。在一个块中分配一个大内存池。使所有分配都使用池(通过 STL 分配器参数),并在客户端退出时释放整个池。

【讨论】:

  • 线程没有问题 - 如果我从线程中删除代码(让它为空),内存就可以了。我创建的长字符串仅用于测试 - 因为如果它只是短字符串,则差异太小。在实际程序中,有时(将会)每秒有 1000 个请求,有时几分钟内没有请求,但一段时间后,程序会分配超过 gig 的内存。线程管理我已按全局 bool 数组排序,该数组设置为 false如果线程完成然后下一个请求可以使用它的变量..问题是我无法在字符串之后清除内存
  • @tominko:正如我在回答中试图说明的那样,线程使堆碎片更加严重。每线程自定义内存分配器池或线程感知全局分配器将帮助您解决问题。
  • 有趣的是,大多数问题也可以通过使用更少的线程并加入它们来减少。
【解决方案3】:

根据 new 和 delete(或 free() 和 malloc())的实现,您的内存在删除/释放后可能不会归还给操作系统。这样做不需要内存分配器。内存可能仍会被后续的新内存回收,或者在内部内存碎片整理或垃圾回收之后,内存使用量可能会再次减少。

【讨论】:

  • malloc、realloc 和 free 运行良好,我在活动监视器中直接看到了区别,但 'char* 方法' - 我在另一个测试类字符串中完成不包括我需要的函数在 std::string
【解决方案4】:

我怀疑您看到的是内存碎片问题,而不是内存泄漏问题。由于您不等待线程退出,因此您有多达 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 GB;但修改后的版本总共分配了大约 620 MB。

另外值得注意的是,Valgrind 没有显示任何内存泄漏。如果您认为您的程序中存在内存问题,尝试一下总是一个好主意:Valgrind home

【讨论】:

  • 问题不是分配 - 即使我不知道创建字符串的最大大小 - 我有另一个版本,我正在使用“我的字符串”类,它是带有 malloc 的 char* 和免费的,它工作得很好——但我需要使用 std::string 函数,比如 find_first_not_of... (这需要很长时间才能实现到我的字符串类)——我需要以某种方式在字符串之后清理
  • @tominko:不要忘记,如果您在 char 缓冲区中有一个字符串,那么旧的 C 字符串函数仍然可以工作。您可以使用#include &lt;cstring&gt; 并使用strcspn 替换find_first_not_of
  • @Zan Lynx:我想我必须摆脱 std::string ;-(
  • @tominko 不,你不这样做,你必须修复你的代码,不要一次做那么多。
【解决方案5】:

通过将“线程例程”放在另一个范围内解决的问题 - if (1) {..} 因为正如我在几个论坛上发现的那样 - 线程在退出时没有调用析构函数,那么在这种情况下(没有添加 if 块)每个创建的线程都会为“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);
}

...我在我的项目中发现了一些小错误,但这是主要问题

【讨论】:

  • 您可以使用{ } 添加范围。您不需要 if (1) 部分。
  • 您忘记接受您的回答。去做吧,因为会登上顶峰,会帮助别人
猜你喜欢
  • 2011-05-14
  • 1970-01-01
  • 2021-05-26
  • 2011-03-26
  • 1970-01-01
  • 2011-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多