1. 程式人生 > >STL記憶體分配方式

STL記憶體分配方式

有感於STL的記憶體管理  
警告:本文是技術類文章,只適合碼工們圍觀,非碼工請跳過此坑

1. 背景
前些天在一個技術分享會上,某大牛說,STL使用了記憶體池,釋放記憶體的時候,並不釋放給OS,而是自己由留著用。
聽到這些觀點後,我就有些著急了,因為我以前一直是直接使用STL的一些工具類的,比如std::string、std::map、std::vector、std::list等等,從來都沒有關注過記憶體的問題。
帶著記憶體的問題,我花了兩三天的時間去閱讀STL的程式碼,並且寫一些簡單的程式進行測試;下面列舉一些心得體會,但是卻沒有什麼大的結論 -.- 

2. 容易誤解的簡單例子
我們以STL中的map為例,下面有一個使用map的簡單例子,大部分人可以在30秒內寫好。


[cpp] view plain copy  print?
  1. void testmap()  
  2. {  
  3.   map<intfloat> testmap;  
  4.   for (int i = 0; i < 1000000; i++) {  
  5.     testmap[i] = (float)i;  
  6.   }  
  7.   testmap.clear();  
  8. }  

為了在呼叫map::clear()之後檢視程序的記憶體使用量,我們可以加幾行程式碼讓程式暫停一下。

[cpp] view plain copy  print?
  1. void testmap()  
  2. {  
  3.   map<int
    float> testmap;  
  4.   for (int i = 0; i < 1000000; i++) {  
  5.     testmap[i] = (float)i;  
  6.   }  
  7.   testmap.clear();  
  8.   // 觀察點
  9.   int tmp; cout << "use ps to see my momory now, and enter int to continue:"; cin >> tmp;  
  10. }  

編譯執行上面的程式,你會看見這樣的情況:ps顯示程序的記憶體使用量為40MB多。這時,你會毫不猶豫地說,STL的map使用了記憶體池(memory pool)。

然後,我就跑去閱讀libstdc++的STL的原始碼,STL提供了很多種Allocator的實現,有基於記憶體池的,但是預設的std::allocator的實現是new_allocator,這個實現只是簡單的對new和delete進行了簡單的封裝,並沒有使用記憶體池。這樣,懷疑的物件就轉移到glibc的malloc函數了。malloc提供的兩個函式來檢視當前申請的記憶體的狀態,分別是malloc_stats()和mallinfo(),它們都定義在<malloc.h>裡。

為了弄清楚這個問題,我們對上面的例子進行如下的改造:


[cpp] view plain copy  print?
  1. #include <malloc.h>
  2. void testmap()  
  3. {  
  4.   malloc_stats();        // <======== 觀察點1
  5.   map<intfloat> testmap;  
  6.   for (int i = 0; i < 1000000; i++) {  
  7.     testmap[i] = (float)i;  
  8.   }  
  9.   malloc_stats();        // <======== 觀察點2
  10.   testmap.clear();  
  11.   malloc_stats();        // <======== 觀察點3
  12. }  

這個例子的執行環境是這樣的:

[[email protected] ~]$ g++ -v
Reading specs from /usr/lib/gcc/x86_64-redhat-linux/3.4.6/specs
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --disable-checking --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-java-awt=gtk --host=x86_64-redhat-linux
Thread model: posix
gcc version 3.4.6 20060404 (Red Hat 3.4.6-9)


程式的執行結果是這樣的:


在觀察點1:
*       system bytes     =          0
*       in use bytes     =          0
在觀察點2:
*       system bytes     =          48144384
*       in use bytes     =          48005120
在觀察點3:
*       system bytes     =          48140288    <==== malloc cache the memory here
*       in use bytes     =          5120


很明顯,儘管程式設計師顯式地呼叫了map::clear(),但是malloc並沒有把這些記憶體歸還給OS,而是快取起來了。所以說,這個例子的罪魁禍首並不是libstdc++的的STL,而是glibc的malloc。


3. 侯捷的《STL原始碼剖析》有點過時了
在除錯上面的例子的時候,我在看了不少的書籍和網上的文章,其中就包括了侯捷的《STL原始碼剖析》,但是這本書已經過時了,因為他寫這本書的時候,g++的版本才2.9。我把g++的各個版本的原始碼都下載下來了,並且進行了比較,總結如下:
侯捷的《STL原始碼剖析》只對於gcc-3.3.*及以前的版本是對的;對於gcc-3.4.*以後的版本,STL中關於記憶體的程式碼變了
當前,大家使用的gcc大都是3.4.6版本或者更加新的版本
gcc-3.3分支從2003-05-13釋出第1版,到2005-05-03釋出3.3.6
gcc-3.3的預設的Allocator,定義在"include/bits/stl_alloc.h"裡,確實是帶有cache的 (即常說的memory pool)
gcc-3.4的預設的Allocator,定義在"include/bits/allocator.h"裡,它的真實的實現是"include/ext/new_allocator.h",這個實現不帶cache,只是new和delete的簡單封裝


4. STL記憶體管理的基礎知識(gcc-3.4.*及以後的) 

通過這次對STL的研究,我學到不不少新的知識。可能這些內容你都已經會了,-.-,我比較弱,下面的內容我是第一次知道的:


STL有很多種allocator,預設採用的是std::allocator,我們沿著這樣的標頭檔案路線,可以找到它的最終實現:
-> "include/bits/allocator.h"
-> "include/i386-redhat-linux/bits/c++allocator.h"
-> "include/ext/new_allocator.h"(即是說,std::allocator == __gnu_cxx::new_allocator)

根據C++的標準,STL的allocator,把物件的申請和釋放分成了4步:
第1步:申請記憶體空間,對應函式是allocator::allocate()
第2步:執行建構函式,對應函式是allocator::construct()
第3步:執行解構函式,對應函式是allocator::destroy()
第4步:釋放記憶體空間,對應函式是allocator::deallocate()
STL崇尚拷貝,你往容器裡放東西或者從容器裡取東西,都是要呼叫拷貝建構函式的。比如,你有一個物件a要插入到map裡,過程是這樣的:
map先申請一個結點的空間
呼叫拷貝建構函式初始化該結點
把新結點插入到map的紅黑樹中

STL中實現了好多種不同的更為具體的allocator,如下(GNU GCC關於Memory的官方文件):
__gnu_cxx::new_allocator: 簡單地封裝了new和delete操作符,通常就是std::allocator
__gnu_cxx::malloc_allocator: 簡單地封裝了malloc和free函式
__gnu_cxx::array_allocator: 申請一堆記憶體
__gnu_cxx::debug_allocator: 用於debug
__gnu_cxx::throw_allocator: 用於異常
__gnu_cxx::__pool_alloc: 基於記憶體池
__gnu_cxx::__mt_alloc: 對多執行緒環境進行了優化
__gnu_cxx::bitmap_allocator: keep track of the used and unused memory locations.
上面的8個allocator的實現中,bitmap_allocator、pool_allocator和__mt_alloc是基於cache的,其它的不基於cache
* 那麼?如何指定使用一個特殊的allocator呢?示例如下:
map<int, int> a1;                                    // 方法1
map<int, int, less<int>, std::allocator<pair<int, int> > > a3;      // 方法2
// 方法3,方法1、方法2、方法3都是等價的
map<int, int, less<int>, __gnu_cxx::new_allocator<pair<int, int> > > a2;  
// 方法4,使用了基於cache的allocator
map<int, int, less<int>, __gnu_cxx::__pool_alloc<pair<int, int> > >  a4;  


5. 記憶體碎片是容易被忽視的導致OutOfMemory的原因


這個觀點有點類似於磁碟碎片,也可以稱為記憶體碎片吧,當記憶體碎片過多的時候,極容易出現OutOfMemory錯誤;


使用STL的map特別容易出現這種情況,往map裡插入了海量的小物件,然後釋放了一些,然後再想申請記憶體時,就出現OutOfMemory錯誤了;


這種現象不只是在使用STL的情況會發現,下面舉一個例子來說明記憶體碎片的問題,儘管這個例子沒有使用STL。


舉例之前,先說明一下這個例子中使用的兩個檢視當前程序的記憶體統計量的2個函式:
int get_max_malloc_length_inMB() : 得到當前可以申請的最長的記憶體長度(MB);這個函式不停地呼叫p=malloc(length*1024*1024);如果成功,則length++,並且free(p);如果失敗,返回(length-1)。
int get_free_mem_inKB() : 得到當前可以申請的記憶體總量(KB);這個函式不停地呼叫malloc(1024)來申請1KB的記憶體;如果成功,把這1KB的記憶體存起來,並且count++;如果失敗,則把所有的1KB記憶體釋放,再返回count。
為了測試方便,我在執行程式前,設定了程序的最大記憶體為200MB,使用的命令如下:


ulimit -m 204800;
ulimit -v 204800;


這個例子把申請到的記憶體以矩陣的形式儲存起來,先按列優先把指標存起來,再按行優先進行free,這樣會造成大量的記憶體碎片;例子的虛擬碼如下:
typedef char* PtrType;
PtrType ** Ptrs = (PtrType**) malloc( ROW * sizeof(PtrType*) );
...


// 第1步: 佔領所有的記憶體,按列優先進行申請
[cpp] view plain copy  print?
  1. for(j=0; j<COL; ++j) {  
  2.     for(i=0; i<ROW; ++i) {  
  3.         Ptrs[j][i] = malloc(1024);  
  4.     }  
  5. }  

// 第2步:按行優先釋放所有的記憶體,在中間多次呼叫get_max_malloc_length_inMB和get_free_mem_inKB來檢視記憶體使用情況
[cpp] view plain copy  print?
  1. for (i=0; i<ROW; ++i) {  
  2.     for (j=0; j<COL; ++j) {  
  3.         free( Ptrs[i][j] );  
  4.     }  
  5.     free(Ptrs[i]);  
  6.     // 得到兩個關於記憶體的統計量
  7.     get_max_malloc_length_inMB();  
  8.     get_free_mem_inKB();  
  9. }  

// 第3步:釋放Ptrs,再獲取一次記憶體的統計量
[cpp] view plain copy  print?
  1. free(Ptrs);  
  2. get_max_malloc_length_inMB();  
  3. get_free_mem_inKB();  

需要關注的是,記憶體的申請的順序是按列優先的,而釋放的順序是按行優先的,這種做法就是模擬記憶體的碎片。<BR>
執行上面的程式後,得到的結果是:在釋放記憶體的過程中,max_malloc_length_inMB長期保持在0 MB,當全部釋放完後,max_malloc_length_inMB變成了 193 MB<BR>
max_malloc_length_inMB: 
    196 MB -> 0 MB -> 0 MB -> ... -> 0 MB -> 0 MB -> ... 
           -> 0 MB -> 0 MB -> 195 MB
free_mem_inKB: 
    199374 KB -> 528 KB -> 826 KB -> ... -> 96037 KB -> 96424 KB -> ... 
              -> 197828 KB -> 198215 KB -> 198730 KB


上面的結果引申出這樣的結論:
OutOfMemory錯誤,並不一定是記憶體使用得太多;
當一個程式申請了大量的小記憶體塊 (比如往std::map中插入海量的小物件),導致記憶體碎片過多的話,一樣有可能出現OutOfMemory錯誤


6. 一些別的收穫
6.1 libc.so.6和glibc-2.9有什麼不同?
參考文獻:http://en.wikipedia.org/wiki/GNU_C_Library
在80年代,FSF寫了glibc;
後來,linux kernel的人照著glibc,寫了"Linux libc",一直從libc.so.2到libc.so.5
到1997年,FSF釋出了glibc-2.0,這個版本有很多優點,比如支援有更多的標準,更可移植;linux kernel的人就把"Linux libc"的專案砍掉了,重新使用glibc-2.0,然後就命名為libc.so.6
如果你執行一下這個命令"ls -lh /lib/libc.so.6",你會發現它其實是一個符號連結,在我的電腦上,它指向了"/lib/libc-2.9.so"
6.2 申請記憶體的方式共有多少種?
參考文獻:glibc manual中的第3章(見http://www.gnu.org/software/libc/manual/)
exec
fork
程序內:
global var or static var
local var
malloc()
memory map file

相關推薦

STL記憶體分配方式

有感於STL的記憶體管理  警告:本文是技術類文章,只適合碼工們圍觀,非碼工請跳過此坑1. 背景前些天在一個技術分享會上,某大牛說,STL使用了記憶體池,釋放記憶體的時候,並不釋放給OS,而是自己由留著用。聽到這些觀點後,我就有些著急了,因為我以前一直是直接使用STL的一些

記憶體分配方式以及堆和棧的區別

轉載:https://blog.csdn.net/shanchangyi/article/details/51854795 對於一個程式要執行,涉及到的記憶體分配是一個首要問題,這裡簡單說一下一個簡單的程式執行所涉及到的記憶體分配方式。另外,在資料結構中存在堆和棧的概念,棧是一種先進後出的資料結

C++記憶體分配方式-堆、棧、靜態儲存區、常量儲存區

C++中,記憶體分為5個區:堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區。棧:是由編譯器在需要時自動分配,不需要時自動清除的變數儲存區。通常存放區域性變數、函式引數等。堆:是由new分配的記憶體塊,由程式設計師釋放(編譯器不管),一般一個new與一個delete對應,一個new[]與一個del

記憶體分配方式詳解(堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區)

原文地址:https://blog.csdn.net/u013007900/article/details/79338653 參考文章:http://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html 一、資料結構中的棧和堆 雖

資料儲存方式記憶體分配方式

資料儲存方式: 1、未初始化的全域性變數(.bss段) 2、初始化過的全域性變數(.data段) 3、常量資料(.rodata段) 4、程式碼(.text段) 5、棧(stack) 6、堆(heap) 記憶體分配方式: 1、從靜態儲存區域分配。記憶體在程式編譯時就已經

記憶體分配方式,堆區,棧區,new/delete/malloc/free

1.記憶體分配方式 記憶體分配方式有三種: [1]從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。 [2]在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運

C/C++記憶體分配方式與儲存區

C/C++記憶體分配有三種方式: [1]從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。 [2]在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處

c++記憶體分配方式,堆與棧區別

1)棧區(stack):由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。 2)堆區(heap):一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由OS回收。注意它與資料結構中的堆是兩回事,分配方式倒是類似於連結串列。 3)全域性/靜態區

Qt總結之十二:C/C++記憶體分配方式與儲存區

一、C/C++記憶體分配有三種方式   從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元

C語言記憶體分配方式及malloc,realloc,calloc,alloc.free函式

C語言跟記憶體分配方式   (1) 從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。   (2)在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元

淺談記憶體分配方式以及堆和棧的區別(很清楚)

對於一個程式要執行,涉及到的記憶體分配是一個首要問題,這裡簡單說一下一個簡單的程式執行所涉及到的記憶體分配方式。另外,在資料結構中存在堆和棧的概念,棧是一種先進後出的資料結構,堆則是一種排序方式,而在記憶體分配中也存在堆(heap)和棧(stack)的概念,與資料結構中的概

Java 陣列記憶體分配方式

Java中,宣告陣列的語法有兩種: 1.資料型別 陣列名[]; 2.資料型別[] 陣列名; 例如: int a[]; 與 int[] a;都是表示聲明瞭一個整型陣列a 二維陣列的宣告也類似。 來看看二維陣列存分配方式: 例如:int a[][] = new int[2][

Linux_C++記憶體分配方式詳解——堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區

棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變數的儲存區。裡面的變數通常是區域性變數、函式引數等。在一個程序中,位於使用者虛擬地址空間頂部的是使用者棧,編譯器用它來實現函式的呼叫。和堆一樣,使用者棧在程式執行期間可以動態地擴充套件和收縮。   堆,就是那些

記憶體分配方式和控制記憶體分配

    記憶體管理是C++最令人切齒痛恨的問題,也是C++最有爭議的問題,C++高手從中獲得了更好的效能,更大的自由,C++菜鳥的收穫則是一遍一遍的檢查程式碼和對C++的痛恨,但記憶體管理在C++中無處不在,記憶體洩漏幾乎在每個C++程式中都會發生,因此要想成為C++高手,

c++ stl記憶體分配

STL原始碼剖析 SGI stl中stl 的記憶體分配不是採用allocator類,而是採用自己寫的類alloc 這個alloc類中主要有四個函式 construct : 用於呼叫新建類的建構函式,其實現就是依靠placement new destroy

C++ STL 記憶體分配的思想以及使用union(共用體)的妙處

STL空間配置器 前言: 今天看書《STL原始碼剖析》,書中說道:空間配置器;空間配置器是用來為容器分配記憶體的一個東西,空間配置器中有兩種配置器:第一級配置器,第二級配置器 當用戶申請的記憶體小於128bytes的時候,用第二級配置器,當用戶申請的記憶體大於128byt

不同記憶體分配方式的區別 VirtualAlloc HeapAlloc malloc new

在進行Windows的學習過程中,經常看到不同的記憶體分配方式,例如VirtualAlloc, HeapAlloc, malloc和new。它們之間存在一些差異。 (1) VirtualAlloc PVOID VirtualAlloc(PVOID pvAddress, S

理順一下C++三種記憶體分配方式

C/C++記憶體分配有三種方式:[1]從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。靜態分配的區域的生命期是整個軟體執行期,就是說從軟體執行開始到軟體終止退出。只有軟體終止執行後,這塊記憶體才會被系統回收[2]在棧上建立。在執行

MYSQL,innodb_buffer_pool_size記憶體分配方式

以前一直以為MYSQL,innodb_buffer_pool_size=8G,MySQL一起動就會將佔用掉8G記憶體(認為TOP可以看到記憶體被使用了8G),但是最近才仔細研究一下,原來不是這樣的(可能自己對Linux malloc記憶體分配也只是知道了個皮毛吧),MyS

jvm 的記憶體分配方式

//這篇部落格講啦如何操作 https://blog.csdn.net/jintaohahahaha/article/detail