2013-02-15 42 views
2

我正在使用C++並使用multimap來存儲數據。如何減少C++中數據的內存大小?

struct data 
{ 
     char* value1; 
     char* value2; 

     data(char* _value1, char* _value2) 
     { 
      int len1 = strlen(_value1); 
      value1 = new char[len1+1]; 
      strcpy(value1,_value1); 

      int len2 = strlen(_value2); 
      value2 = new char[len2+2]; 
      strcpy(value2,_value2); 
     } 
     ~data() 
     { 
      delete[] value1; 
      delete[] value2; 
     } 
} 

struct ltstr 
{ 
    bool operator()(const char* s1, const char* s2) const 
    { 
      return strcmp(s1, s2) < 0; 
    } 
}; 


multimap <char*, data*, ltstr> m; 

樣品輸入:

Key    Value 
    ABCD123456  Data_Mining Indent Test Fast Might Must Favor List Myself Janki Jyoti Sepal Petal Catel Katlina Katrina Tesing Must Motor blah blah. 
    ABCD123456  Datfassaa_Minifasfngf Indesfsant Tfdasest Fast Might Must Favor List My\\fsad\\\self Jfasfsa Katrifasdna Tesinfasfg Must Motor blah blah. 
    tretD152456  fasdfa fasfsaDfasdfsafata_Mafsfining Infdsdent Tdfsest Fast Might Must Favor List Myself Janki 

有在輸入端27萬個條目。 輸入大小= 14GB

但我注意到內存消耗達到56 GB。我可以知道如何減少內存大小?

+2

您是否嘗試過輕量級設計模式? – Deamonpog 2013-02-15 16:33:35

+1

您可以查看[boost平面關聯容器](http://www.boost.org/doc/libs/1_53_0/doc/html/container/non_standard_containers.html#container.non_standard_containers.flat_xxx)。當然,性能權衡,特別是插入。 – juanchopanza 2013-02-15 16:39:11

+0

我不想使用任何外部庫。根據我的項目指南,我應該使用C++標準庫。 – Manish 2013-02-15 16:40:50

回答

4

如果你不能減少實際存儲的數據量,你可能想嘗試使用一個開銷較小的不同容器(map和multimap有相當多的),或者找到一種方法來保持只有一部分的內存中的數據。

你可能想看看這些庫:

+1

根據數據,可能會減少所需的實際存儲量。例如,存儲可以使用trie或DAWG的詞彙表,導致STXXL的主要節省 – SomeWittyUsername 2013-02-15 16:43:08

+1

+1。適合這種情況。 – leemes 2013-02-15 17:01:23

2

第一種優化將存儲data物體代替指針

std::multimap <char*, data, ltstr> m; 

因爲使用data*會增加分配的額外內存開銷。

另一個是使用pool allocator/Memory pool來減少動態內存分配的佔用空間。

如果你有很多相同的密鑰字符串,你也可以改進它,如果你可以重用密鑰。

3

一種可能性是使用std::map<char *, std::vector<data> >而不是多圖。在multimap中,您將在每個條目中存儲密鑰字符串。使用地圖,您只需要密鑰字符串的一個副本,並附加多個data項目。

2

沒有看到你的一些數據,有幾件事情可以改善你的項目的內存使用情況。

首先,正如Olaf所建議的那樣,將數據對象存儲在multimap中,而不是指向它的指針。雖然我不建議在數據結構中使用池,但與直接將其存儲在地圖中相比,它不會節省內存而使事情複雜化。

你可以做什麼,雖然是一個專門的地圖分配器,分配std::pair<char*, data>對象。這可以節省一些開銷和堆碎片。

接下來,您應該關注的主要問題是嘗試擺脫數據中的兩個char*指針。有14個演出的數據,必須有一些重疊。根據它是什麼數據,你可以存儲它有點不同。

例如,如果數據是名稱或關鍵字,那麼將它們存儲在中央散列中是有意義的。是的,像上面提到的DAWG一樣有更復雜的解決方案,但我認爲應該首先嚐試簡單的解決方案。

通過簡單地將它存儲在std::set<std::string>中並將迭代器存儲到它中,您可以壓縮所有可以節省大量數據的重複項。這雖然假設你不刪除字符串。刪除字符串將需要你做一些引用計數,所以你會使用類似std::map<std::string, unsinged long>。我建議你編寫一個繼承自/包含這個散列的類,而不是把引用計數邏輯放入你的數據類中。

如果您要存儲的數據沒有多個重疊,例如因爲它是二進制數據,那麼我建議你將它存儲在std::stringstd::vector<char>中。原因是因爲現在你可以擺脫數據結構中的邏輯,甚至用std::pair替換它。

我還假設你的密鑰不是你存儲在數據結構中的指針之一。如果是這樣,那麼一定要擺脫它,並在您的multimap中使用std::pairfirst屬性。

根據所存儲的數據類型不同,可能會進行進一步的改進。

所以,有很多,可能並不適用於您的數據假設你可以有少這樣的:

typedef std::set<std:string> StringMap; 
typedef StringMap::const_iterator StringRef; 
typedef std::multimap<StringRef, std::pair<StringRef, StringRef>> DataMap; 
2

我會懷疑你泄露或不必要的密鑰存儲複製。關鍵字char *來自哪裏以及如何管理他們的記憶?

如果它們與數據對象中的字符串相同,請考慮使用multiset<data *, ltdata>而不是multimap

如果有許多重複字符串,請考慮合併set<char *,ltstr>中的字符串以消除重複項。

+0

是的,有多餘的按鍵。每個密鑰都有22個值,這就是爲什麼使用multimap。 – Manish 2013-02-16 07:08:40

2

我仍然不完全確定這裏發生了什麼,但似乎內存開銷至少是問題的一部分。但是,整體內存消耗約爲data結構所需的4倍。如果有27M記錄佔用14GB,則每個記錄大約有500個字節,但佔用的空間是56GB。對我而言,這表示存儲的數據比我們在此處顯示的要多,或者至少有一些數據存儲了多次。

而「堆存儲的額外數據」並不是真的爲我做。在Linux中,內存分配需要大約32個字節的數據。 16字節的開銷,分配的內存佔用16個字節的倍數。

所以對於存儲在多重映射一個data *記錄,我們需要:

16 bytes of header for the memory allocation 
8 bytes for pointer of `value1` 
8 bytes for pointer of `value2` 
16 bytes of header for the string in value1 
16 bytes of header for the string in value2 
8 bytes (on average) "size rounding" for string in value 1 
8 bytes (on average) "size rounding" for string in value 2 

?? bytes from the file. (X) 

80 + X bytes total. 

然後我們有char *在多重映射:

16 bytes of header for the memory allocation. 
8 bytes of rounding on average. 

?? bytes from the file. (Y) 

24 + Y bytes total. 

multimap中的每個節點有兩個指針(我假設它是某種二叉樹):

16 bytes of header for the memory allocation of the node. 
8 bytes of pointer to "left" 
8 bytes of pointer to "right" 

32 bytes total. 

所以,tha t會爲文件中的每個條目創建136個字節的「開銷」。對於27M的記錄,這只是超過4GB。

正如我所說,該文件包含每個條目500字節,因此使得14GB。

總共18GB。

所以,在某個地方,有些東西要麼泄漏,要麼數學是錯誤的。我可能會在這裏計算結果,但即使上面的所有數據都是我計算的空間的兩倍,但仍有20GB的數據不明。

當然,還有一些事情我們可以做,以節省內存:

1)不要在data分配兩個字符串。第一計算兩個長度,分配的存儲器的一個塊,並且在彼此之後立即存儲字符串:

data(char* _value1, char* _value2) 
    { 
     int len1 = strlen(_value1); 
     int len2 = strlen(_value2); 
     value1 = new char[len1 + len2 +2]; 
     strcpy(value1,_value1); 

     value2 = value1 + len1 + 1; 
     strcpy(value2,_value2); 
    } 

這將節省每條目平均24個字節。我們可以通過聰明並一次分配數據,值1和值2的內存來節省更多。但這可能有點「太聰明」了。

2)分配一大塊data物品,並且每次放一個物品也會有所幫助。對於這項工作,我們需要一個空的構造,以及「setValues方法」的方法:

struct data 
{ 
    ... 
    data() {}; 
    ... 
    set_values(char* _value1, char* _value2) 
    { 
     int len1 = strlen(_value1); 
     int len2 = strlen(_value2); 
     value1 = new char[len1 + len2 +2]; 
     strcpy(value1,_value1); 

     value2 = value1 + len1 + 1; 
     strcpy(value2,_value2); 
    } 
} 

std::string v1[100], v2[100], key[100]; 

for(i = 0; i < 100; i++) 
{ 
    if (!read_line_from_file(key[i], v1[i], v2[i])) 
    { 
     break; 
    } 
}  

data* data_block = new data[i]; 

for(j = 0; j < i; j++) 
{ 
    data_block[j].setValues[v1[j].c_str(), v2[j].c_str()); 
    m.insert(key[i].c_str(), &data_block[j]); 
} 

再次,這將不保存的內存量巨大,但每個16字節的區域可以節省一些內存。以上當然不是完整的代碼,更多的是「如何完成」的說明。 3)我仍然不確定「密鑰」來自multimap的位置,但如果密鑰是value1和value2條目之一,那麼您可以重用其中的一個,而不是存儲另一個副本[假設這是目前的做法]。

對不起,如果這不是一個真正的答案,但我確實相信這是一個答案,「某處,某事在你解釋你正在做的事情時不知情」。

瞭解在程序中分配的內容肯定會有所幫助。