1

我正在用 C++ 编写处理包含百万节点信息的大量流数据。我使用向量来存储每个节点的名称和索引映射。

现在的问题是 vector 占用的内存比预期的要多得多,而且它们的破坏是无法解释的。

假设某个文件包含 100 万行,每行超过 50 个字符。将它们读入两次,然后检查进程的内存使用情况和向量的估计内存使用情况。它们在 60 MB 上有所不同。这只是我遇到的更大问题的一个小预测,它可能在 GB 规模上有所不同。

我在 Windows7 SP1 Ultimate 64bit 上使用 VS2010 SP1 使用 x86 设置编译程序。

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <Windows.h>
#include <Psapi.h>
using namespace std;

//#define COUNT 500000
int COUNT = 0;

vector<string> namesVector;
map<string,int> namesMap;
void ProcessStatistics()
{   
    PROCESS_MEMORY_COUNTERS memCounter;
    GetProcessMemoryInfo(GetCurrentProcess(),&memCounter,sizeof(memCounter));
    cout<<"Mem Usage by Process: "<<memCounter.WorkingSetSize * 1.0e-6f<<" MB."<<endl;
}
void VectorMemUsage()
{   
    COUNT = namesVector.size();
    int overhead = StringOverhead();
    double mem = 0;
    mem += sizeof(vector<string>);
    mem += overhead*COUNT;
    for(int i=0; i<COUNT; i++)
    {
        mem += namesVector[i].capacity();
    }
    cout<<"Calculated String Vector Usage: "<<mem * 1.0e-6f<<" MB of "<<COUNT<<" strings."<<endl;
}
int StringOverhead()
{
    int overhead = sizeof(string);
    cout<<"String overhead: "<<overhead<<" Bytes."<<endl;   
    return overhead;
}

void main(){
    const std::string infile = "somefile";
    ifstream infstream(infile);
    string s;   
    while(getline(infstream,s) != NULL)
    {
        namesVector.push_back(s);
        //namesMap.insert(pair<string,int>(s,namesVector.size()));
    }
    infstream.clear();
    infstream.seekg(0,ios::beg);    
    while(getline(infstream,s) != NULL)
    {
        namesVector.push_back(s);
        //namesMap.insert(pair<string,int>(s,namesVector.size()));
    }
    //Check process and vector memory usage:
    ProcessStatistics();
    VectorMemUsage();
    System("pause");

    //Release the vector.
    cout<<"Now releasing the memory..."<<endl;        
    //vector<string>(namesVector).swap(namesVector);
    //vector<string>().swap(namesVector); //Deallocate Vector
//map<string,int>().swap(namesMap);   //Deallocate Map
    cout<<"Capacity of vector "<<namesVector.capacity()<<endl;
    ProcessStatistics();
 }

x86 版本的程序输出如下:

Mem Usage by Process: 336.523 MB.
String overhead: 28 Bytes.
Calculated String Vector Usage: 301.599 MB of 3385108 strings.
Press any key to continue . . .
Now releasing the memory...
Mem Usage by Process: 7.64314 MB.

当我在向量上调用 namesVector.shrink_to_fit() 或 vector(namesVector).swap(namesVector) 成语时,向量容量确实减少了,但是内存使用率很高,有人知道解决这个问题吗?交换技巧应该是指针交换吗?为什么它会涉及内存复制和所有并导致这种情况?

Mem Usage by Process: 336.536 MB.
String overhead: 28 Bytes.
Calculated String Usage: 301.599 MB of 3385108 strings.
Vector Capacity is 3543306.
Calculated String Vector Usage: 315.693 MB of 3385108 strings.
Now releasing the memory...
Capacity of vector 3385108
Mem Usage by Process: 434.5 MB.

当我为字符串索引添加映射时,发生了意外行为。当我同时调用vector().swap(namesVector)和map().swap(namesMap)时,结果是这样的,这很好,因为内存被释放了。

Mem Usage by Process: 534.778 MB.
String overhead: 28 Bytes.
Calculated String Usage: 301.599 MB of 3385108 strings.
Vector Capacity is 3543306.
Calculated String Vector Usage: 315.693 MB of 3385108 strings.
Press any key to continue . . .
Now releasing the memory...
Capacity of vector 0
Mem Usage by Process: 8.2903 MB.

但是当我只调用vector().swap(namesVector)时,内存被部分释放了。部分我的意思是它释放的比上面的结果少,大约 336 MB。

Mem Usage by Process: **534.77** MB.
String overhead: 28 Bytes.
Calculated String Usage: 301.599 MB of 3385108 strings.
Vector Capacity is 3543306.
Calculated String Vector Usage: 315.693 MB of 3385108 strings.
Press any key to continue . . .
Now releasing the memory...
Capacity of vector 0
Mem Usage by Process: **440.459** MB.

或者map().swap(namesMap),内存几乎完全没有释放。

Mem Usage by Process: **534.774** MB.
String overhead: 28 Bytes.
Calculated String Usage: 301.599 MB of 3385108 strings.
Vector Capacity is 3543306.
Calculated String Vector Usage: 315.693 MB of 3385108 strings.
Press any key to continue . . .
Now releasing the memory...
Capacity of vector 3543306
Mem Usage by Process: **535.441** MB.

我无法解释发生了什么。有人知道这里发生了什么吗?

谢谢您的帮助。

最好的。

4

1 回答 1

2

内存泄漏标记在这里不合适,从来没有任何泄漏的内存——所有内存都可以访问并由仍在范围内的对象拥有。内存泄漏意味着丢失的内存永远无法释放,因为您没有引用它。

VectorMemUsage你应该使用overhead*namesVector.capacity()或者你只计算填充的向量元素,而不是分配但未初始化的内存。为什么该函数仍然使用全局变量?最好把它写成:

void VectorMemUsage()
{   
    int overhead = StringOverhead();
    double mem = 0;
    mem += sizeof(vector<string>);
    mem += overhead*namesVector.capacity();
    for(int i=0; i < namesVector.size(); i++)
    {
        mem += namesVector[i].capacity();
    }
    cout<<"Calculated String Vector Usage: "<<mem * 1.0e-6f<<" MB of " << namesVector.size() <<" strings."<<endl;
}

如果您想避免向量中未使用的容量,您需要提前知道它将有多少元素(即输入文件中有多少行)并用于reserve预先分配正确数量的元素。

当我在vector上调用shrink_to_fit或swap idiom时,vector容量确实减少了,但是内存使用率很高,有人知道解决这个问题吗?交换技巧应该是指针交换吗?

不,如果仅此而已,它不会减少分配的内存!它将元素复制到一个新向量(仅与所需一样大),然后进行指针交换。因此峰值内存更高,因为您暂时拥有所有元素的两个副本。

或者map().swap(),内存接近没有释放。

除非您使用交换技巧(或),否则向量不会释放其内存shrink_to_fit(),它会保持其当前容量,并且只有在您清除它时才会减小其大小。要释放未使用的容量,您必须使用交换技巧或shrink_to_fit(). 所以所有的内存仍然归向量所有。

使用交换技巧是没有意义的std::map,映射永远不会保留未使用的分配内存,因此您可以namesMap.clear()释放映射使用的所有内存。

总之,由于容器的工作方式,这完全是意料之中的。没有泄漏,您只是有无效的假设。

于 2012-12-30T14:31:05.163 回答