如何減少 vector 的內存佔用

2021-02-20 CPP開發者

(給CPP開發者加星標,提升C/C++技能)

https://blog.csdn.net/qiuguolu1108/article/details/107146466

【導讀】:接上一篇《C++ vector內存分配策略淺析》,其中定義了用於測試的類A,本文中也有用到,大家可以點擊連結跳轉至上一篇,了解下類A的定義。本片文章從使用角度講解如何減少vector的內存佔用,內容較多,建議先碼後看。

以下是正文

vector之所以會發生大量的拷貝,是因為其內存分配策略造成的。每當vector的內存不夠用時,vector都會重新申請兩倍的空間,並將之前的元素搬移到新空間。這才是發生拷貝的根源,既然這樣,我們能不能預先給vector申請一定的空間,避免因空間不夠而發生元素搬移。

1、使用reserve()函數預申請空間

reserve()函數可以給vector預先分配指定大小的空間。

vector<A> va;
va.reserve(1024);
cout<<"va size = "<<va.size()<<endl;cout<<"va capacity = "<<va.capacity()<<endl<<endl;
A a;
for(int i=0;i<1024;i++){ va.push_back(a);}
A::dis_copy_construct_count();cout<<endl;
cout<<"va size = "<<va.size()<<endl;cout<<"va capacity = "<<va.capacity()<<endl;

使用reserve()給va預先分配了1024個空間,所以再往va推入1024個元素的時候,並沒有發生多餘的拷貝構造。

通過reserve()給vector預分配空間,確實可以減少元素的拷貝構造,但我們在使用vector時,有時很難確定容器元素的個數。

在使用reserve()時需要自己去平衡,如果reserve()過大,會造成空間的浪費,如果過小還是會發生拷貝構造。

現在又帶來一個問題,如何將vector過多的沒有存放元素的空間還給系統。

2、erase()函數和clear()會減少vector的內存佔用嗎?

上篇文章中,我們說過vector佔用空間的大小,可以通過capacity()函數來查看。

通過一個示例,來驗證一下erase()、clear()會減少vector內存佔用嗎?

vector<int> vi;
for(int i=0;i<65;i++){ vi.push_back(i);}
cout<<"vi size = "<<vi.size()<<endl;cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;

auto itr = find(vi.begin(),vi.end(),30);
vi.erase(vi.begin(),itr);cout<<"vi size = "<<vi.size()<<endl;cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;

vi.clear();cout<<"vi size = "<<vi.size()<<endl;cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;


通過測試我們發現,erase()和clear()只能將vector空間的元素給析構掉,並不能減少vector內存的佔用。

這是侯捷老師《STL源碼剖析》對vector的erase()、clear()函數實現介紹。這也進一步證實了erase()、clear()並不能釋放vector的空間。

3、data()函數可以返回vector元素存放的位置


#include <iostream>#include <vector>
using namespace std;
int main(){ vector<int> vi;
for(int i=0;i<10;i++) { vi.push_back(i); }
int * p = vi.data();
for(int i=0;i<10;i++) { cout<<*p++<<endl; }
return 0;}


data()這個函數不用多說了,通過示例就可以看出這個函數好強大,直接殺入了vector的老巢。


4、swap()函數用於交換兩個vector

swap()函數可以用於交換兩個vector,但是交換了vector的哪些東西?

vector<int> vi0;for(int i=0;i<5;i++){    vi0.push_back(i);}cout<<"&vi0  = "<<&vi0<<endl;cout<<"vi0.data() = "<<vi0.data()<<endl<<endl;

vector<int> vi1;for(int i=0;i<5;i++){ vi1.push_back(i*100);}cout<<"&vi1 = "<<&vi1<<endl;cout<<"vi1.data() = "<<vi1.data()<<endl;
cout<<endl<<"====================="<<endl<<endl;
vi0.swap(vi1);
cout<<"&vi0 = "<<&vi0<<endl;cout<<"vi0.data() = "<<vi0.data()<<endl<<endl;
cout<<"&vi1 = "<<&vi1<<endl;cout<<"vi1.data() = "<<vi1.data()<<endl;


從示例中可以看出,vector的data()發生了交換,但vi0和vi1所在的地址並沒有發生變化。swap()函數還交換了其他內部成員數據,但我們弄清了我們關心的一點,swap並沒有發生空間的大量拷貝,交換的僅僅是兩個空間地址。

補充一點:swap()函數,不僅僅交換兩個容器的內容,同時它們的迭代器、指針和引用也被交換。在swap發生後,原先指向容器中元素的迭代器、指針和引用依然有效,並指向同樣的元素----但是,這些元素已經在另一個容器中了。

5、vector的拷貝構造

使用一個vector去構造器另外一個vector,在構造新的vector時,僅會根據vector實際元素個數去構造新的vector。

vector<int> vi0;vi0.reserve(100);
for(int i=0;i<5;i++){ vi0.push_back(i);}
cout<<"vi0 size = "<<vi0.size()<<endl;cout<<"vi0 capacity = "<<vi0.capacity()<<endl<<endl;

vector<int> vi1(vi0);cout<<"vi1 size = "<<vi1.size()<<endl;cout<<"vi1 capacity = "<<vi1.capacity()<<endl;


雖然vi0的內存空間可以存放100個int,但實際有效元素只有5個int。通過vi0拷貝構造vi1的時候,並不會像vi0那樣佔用100個int空間,而是根據實際元素的個數申請空間,並不會有多餘的空間。

6、使用swap技巧移除多餘的容量

鋪墊了這麼多,回到我們的主題上,如何減少vector的容量?

vector的構造器在構建新的容器時,會自動的去掉多餘的空間,我們可以利用這個特性,結合swap函數去掉vector中多餘的容量。

6.1 方法一:通過定義一個新的vector
#include <iostream>#include <vector>
using namespace std;
int main(){ vector<int> vi; vi.reserve(100); for(int i=0;i<5;i++) { vi.push_back(i); } cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
vector<int> tmp(vi); cout<<"tmp size = "<<tmp.size()<<endl; cout<<"tmp capacity = "<<tmp.capacity()<<endl<<endl;
cout<<"=================="<<endl<<endl;
tmp.swap(vi);
cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
cout<<"tmp size = "<<tmp.size()<<endl; cout<<"tmp capacity = "<<tmp.capacity()<<endl<<endl;
return 0;}


vi有多餘的容量,通過vi構造一個新的容器tmp,tmp沒有多餘的容量,通過swap函數將tmp和vi交換,則tmp變成了有多餘容量的容器。tmp是一個局部變量,在離開其作用域時,會調用vector的析構器,將其自己釋放。

#include <iostream>#include <vector>
using namespace std;
int main(){ vector<int> vi; vi.reserve(100); for(int i=0;i<5;i++) { vi.push_back(i); }
{ vector<int> tmp(vi); tmp.swap(vi); } return 0;}

這樣可以更加快速的消除多餘容量。之前示例,tmp需要在main函數的最後才被銷毀,此處的示例,tmp在swap之後就立即被銷毀。

6.2 方法二:使用臨時變量

上面的方法確實可以消除vector多餘的容量,但不夠優雅,略顯囉嗦。使用臨時變量可以更加簡潔。

#include <iostream>#include <vector>
using namespace std;
int main(){ vector<int> vi; vi.reserve(100); for(int i=0;i<5;i++) { vi.push_back(i); } cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
{ vector<int>(vi).swap(vi); }
cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl;
return 0;}


沒有定義多餘的變量,一行代碼搞定,真的很優雅了。

簡單的說一下:vector<int>(vi)定義一個臨時對象,這個臨時對象通過拷貝構造器構造。臨時對象調用swap成員函數將其自己和vi交換。臨時對象在離開其作用域時被銷毀。

6.3 清空vi

clear()僅會清空容器中的元素,並不能真正的釋放vector佔用的內存。使用swap()可以釋放vector內存。

#include <iostream>#include <vector>
using namespace std;
int main(){ vector<int> vi; vi.reserve(100); for(int i=0;i<5;i++) { vi.push_back(i); } cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
{ vector<int>().swap(vi); }
cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl;
return 0;}


vector<int>()會產生一個臨時對象,這個對象沒有名字,其size和capacity皆為零。


6.4 總結:

去除vi多餘的容量:vector<int>(vi).swap(vi)

將vi的空間清空:vector<int>().swap(vi)

7、使用C++11中shrink_to_fit()函數去除多餘容量

看看官方介紹


#include <iostream>#include <vector>
int main(){ std::vector<int> v; std::cout << "Default-constructed capacity is " << v.capacity() << '\n'; v.resize(100); std::cout << "Capacity of a 100-element vector is " << v.capacity() << '\n'; v.clear(); std::cout << "Capacity after clear() is " << v.capacity() << '\n'; v.shrink_to_fit(); std::cout << "Capacity after shrink_to_fit() is " << v.capacity() << '\n';}


官方給的示例,很容易理解。

#include <iostream>#include <vector>
using namespace std;
int main(){ vector<int> vi; vi.reserve(100);
cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
for(int i=0;i<10;i++) { vi.push_back(i); }
cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
vi.shrink_to_fit(); cout<<"vi size = "<<vi.size()<<endl; cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
return 0;}

vi的容量是100,向其推入10個元素,則有90個多餘的空間,調用shrink_to_fit()後,其容量變為10,釋放了多餘的空間。

8、減少vector容量,必要的拷貝依然存在。

不管是swap()函數還是shrink_to_fit()函數,在去除vector多餘空間的時候,還是會發生必要的元素拷貝。

8.1 swap方法
vector<A> va;va.reserve(10);
A a;
for(int i=0;i<3;i++){ va.push_back(a);}
cout<<endl<<"======================"<<endl<<endl;
vector<A>(va).swap(va);
cout<<endl<<"======================"<<endl<<endl;



這裡的拷貝構造主要是在構造臨時對象產生的。


8.2 shrink_to_fit方法


vector<A> va;va.reserve(10);
A a;
for(int i=0;i<3;i++){ va.push_back(a);}
cout<<endl<<"======================"<<endl<<endl;
va.shrink_to_fit();
cout<<endl<<"======================"<<endl<<endl;

結果都是一樣的,發生了拷貝構造。

9、resize()函數

順便說一下resize()函數,這個函數使用也有一定的迷惑性。現在通過幾個例子說明一下其背後都做了哪些事情。

現在vector有兩個大小,一個是size(),vector實際元素的個數;另一個是capacity(),vector的容量。

size是小於等於capacity的。

9.1 resize()參數小於size()

將多餘的元素析構掉

#include <iostream>#include <vector>
using namespace std;
class A{public: A(int data = 100) :data_(data) { cout<<"constructor : "<<this<<endl; }
A(const A& a) { data_ = a.data_; cout<<this<<" : copy constructor form : "<<&a<<endl; }
void display() { cout<<data_<<" "; }
~A() { cout<<"deconstructor : "<<this<<endl; }
private: int data_;};
int main(){ vector<A> va; va.reserve(8);
A a(0),b(1),c(2),d(3);
cout<<endl<<"========================"<<endl; va.push_back(a); va.push_back(b); va.push_back(c); va.push_back(d);
cout<<endl<<"========================"<<endl; for(auto & i : va) { i.display(); } cout<<endl;
va.resize(2);
for(auto & i : va) { i.display(); } cout<<endl;
return 0;}


9.2 resize()參數大於size(),小於capacity()。9.2.1 情況一:使用默認構造構造新元素
vector<A> va;va.reserve(8);
A a(0),b(1),c(2),d(3);
cout<<endl<<"========================"<<endl;va.push_back(a);va.push_back(b);va.push_back(c);va.push_back(d);
cout<<endl<<"========================"<<endl;for(auto & i : va){ i.display();}cout<<endl;
va.resize(6);
for(auto & i : va){ i.display();}cout<<endl;


需要添加兩個新的元素,沒有指定添加的元素,則調用類A的默認構造生成新元素。

A(int data = 100)  :data_(data){  cout<<"constructor : "<<this<<endl;}

這是類A的默認構造器,使用這個構造器生成的對象,其默認值為100。


9.2.2 情況二:使用指定的元素構造新元素
vector<A> va;va.reserve(8);
A a(0),b(1),c(2),d(3);
cout<<endl<<"========================"<<endl;va.push_back(a);va.push_back(b);va.push_back(c);va.push_back(d);
cout<<endl<<"========================"<<endl;for(auto & i : va){ i.display();}cout<<endl;
A aa(99);
va.resize(6,aa);
for(auto & i : va){ i.display();}cout<<endl;


和上面類似,但新元素的生成,調用了拷貝構造器。但這裡多發生了一次拷貝構造,根據調用情況,猜測resize()先把元素拷貝到其內部,所有新元素的拷貝都是基於resize()內部的副本。

9.3 resize()參數大於capacity()

重新申請空間,將原數據拷貝過來,在新的位置調用默認構造器生成新的元素。

vector<A> va;va.reserve(8);
A a(0),b(1),c(2),d(3);
cout<<endl<<"========================"<<endl;va.push_back(a);va.push_back(b);va.push_back(c);va.push_back(d);
cout<<endl<<"========================"<<endl;for(auto & i : va){ i.display();}cout<<endl;
va.resize(10);
for(auto & i : va){ i.display();}cout<<endl;
cout<<va.capacity()<<endl;


10、總結

本文介紹了如何去除vector多餘的容量。shrink_to_fit()是C++11提供的新方法,在C++98中可以使用swap()函數實現去除多餘的容量。

參考:

《STL源碼剖析》

《Effective STL》

- EOF -

關於如何減少vector的內存佔用,歡迎在評論中和我探討。覺得文章不錯,請點讚和在看支持我繼續分享好文。謝謝!

關注『CPP開發者』

看精選C++技術文章 . 加C++開發者專屬圈子

↓↓↓

點讚和在看就是最大的支持❤️

相關焦點

  • 面試題目--vector的內存管理
    vector另外一個特性在於它的內存空間會自增長,每當vector容器不得不分配新的存儲空間時,會以加倍當前容量的分配策略實現重新分配。例如,當前capacity為50,當添加第51個元素時,預留空間不夠用了,vector容器會重新分配大小為100的內存空間,作為新連續存儲的位置。
  • ​如何使用生成器減少內存佔用,並讓Python代碼運行更快?
    本文轉載自公眾號「讀芯術」(ID:AI_Discovery)如何使用生成器減少內存佔用並讓Python代碼運行更快,關乎你「代碼人生」的生死存亡。 然而,當我剛開始學習Python生成器時,並不知道它最後會顯得如此重要。 但在學習機器學習的過程中需要編寫自定義函數時,它發揮了不可取代的作用。
  • 技能分享:如何用生成器減少內存佔用,讓Python代碼運行更快?
    圖源:Unsplash如何使用生成器減少內存佔用並讓Python代碼運行更快,關乎你「代碼人生」的生死存亡。Secsand {mem_diff} Mb to execute this method")運行後,上述代碼的輸出如下:It took2.9999999995311555e-05 Secs and 0.02656277 Mb to execute this method肉眼可見,執行時間和內存使用量都大大減少了
  • 6 個技巧,提升 C++11 的 vector 性能
    多數實現中,vector 和 string 容量的提升因子在 1.5 和 2 之間。2. 從容器原來佔用的內存中將元素拷貝到新分配的內存中。3. 釋放原有內存中的對象。4. 釋放原有內存。有了所有這些操作:分配、回收、拷貝和釋放,如果說這些步驟(對於性能)極其昂貴,你一點都不應該感到驚訝。當然,你肯定不希望頻繁的進行這樣的操作。
  • C++ vector詳解
    可以簡單的認為,vector是一個能夠存放任意類型的動態數組。接下來,請跟隨小編一起來複習一下吧。以下是正文前言本文mark了vector的一些接口,介紹了vector中的對內存和對象的管理詳解請見cppreference-vector。
  • 詳解C++中的vector 利用swap去除多餘容量
    在使用C++中的 vector的時候,vector的申請的內存不會自動釋放,當 push_back的時候,如果 vector的當前內存不夠使用的時候,vector會自動的二倍增長內存,可能會導致最後內存不斷的增多。下面我們介紹幾種避免這種情況的技巧。
  • C vector詳解
    可以簡單的認為,vector是一個能夠存放任意類型的動態數組。接下來,請跟隨小編一起來複習一下吧。以下是正文前言本文mark了vector的一些接口,介紹了vector中的對內存和對象的管理詳解請見cppreference-vector。
  • 獲得PHP代碼佔用內存的情況
    下面是使用示例:3echo memory_get_usage(), '<br />';        4$tmp = str_repeat('http://www.xxx.net/', 4000);5echo memory_get_usage(), '<br />'; 7echo memory_get_usage();        上面的程序後面的注釋代表了它們的輸出
  • 內存對齊 | 原來欄位順序還能影響結構體佔用的內存空間
    內存對齊對結構體空間的影響在討論內存對齊前我們先看一個思考題,我們都知道Go的結構體在內存中是由一塊連續的內存表示的,那麼下面的結構體佔用的內存大小是多少呢?>僅僅只是調換了一下順序,結構體 ST1 就減少了三分之一的內存佔用空間。
  • 教程 | 簡單實用的pandas技巧:如何將內存佔用降低90%
    數據科學博客 Dataquest.io 發布了一篇關於如何優化 pandas 內存佔用的教程:僅需進行簡單的數據類型轉換,就能夠將一個棒球比賽數據集的內存佔用減少了近 90%,機器之心對本教程進行了編譯介紹。當使用 pandas 操作小規模數據(低於 100 MB)時,性能一般不是問題。
  • win7出現物理內存佔用過高的問題
    而這種問題大多都是因為電腦系統物理內存佔用過高所導致的。為了解決這種問題我們可以嘗試在任務管理器中將多餘的進程關閉,或者是調整系統的虛擬內存即可。詳細解決方法就來看下小編是怎麼做的吧~ win7系統物理內存佔用過高的解決方法
  • C++ vector用法詳解
    vector概述  vector是種容器,類似數組一樣,但它的size可以動態改變。  vector的元素在內存中連續排列,這一點跟數組一樣。這意味著我們元素的索引將非常快,而且也可以通過指針的偏移來獲取vector中的元素。
  • 各個瀏覽器佔用內存測試
    各個瀏覽器佔用內存測試 最近在使用遊覽器的過程中發現,不同遊覽器的內存佔用情況居然差異很大,目測是由於GC(垃圾回收機制)造成的。於是就選取了市面上比較熱門的遊覽器進行測試。
  • 解決內存佔用100%!
    在Win10系統中,我們經常會發現任務管理器中有個System進程,總是佔用cpu內存很高,有時會到百分之百,這也會導致電腦有時卡的根本用不了,而且對於這個進程來說,有時佔用的內存卻是沒有上限的,對於這種情況我們該如何處理呢?下面我們就一起來看看System佔用CPU內存過高的解決方法。
  • Pandas學習筆記(二三):內存佔用
    通常情況下,Pandas對讀取的數據列默認是設置為object數據類型,這種通用類型因自身的兼容性會導致所讀取的數據佔據較大的內存空間,倘若能給它們設置合適的數據類型,就可以降低該數據集的實際內存佔用,從而提升運行效率。
  • C++逆向學習(二) vector
    現在的逆向C++題越來越多,經常上來就是一堆容器、標準模板庫,這個系列主要記錄這些方面的逆向學習心得本文主要介紹std::vector,因為逆向題中的C++代碼可能會故意寫的很繞,比如輸入一個數組,直接給vector賦值即可,但是也可以用稍微費解的方法連續push_back(),也算是一種混淆的手段,文章中的示例會逆向一些故意寫的繁瑣的程序vector內存布局
  • ​跟我學C++中級篇——STL的容器vector
    一、順序容器vectorC++程式設計師中,如果用到過STL,那麼一定肯定用過vector,這個是最常見,最初步的一個數據類型。上一篇提到的array遠遠比不上它。畢竟那玩意兒相對vector是很久遠後才提出來的。在這之前,std::vector承擔了多少小菜鳥處理數組各種問題的最優選方法。不用處理內存,可以刪除,任意增加不考慮越界。那簡直是一種最單純質樸的快樂。
  • C++打怪 之 vector
    但是設置過大,也會導致內存浪費,雖然不是什麼大問題,但這種變量若定義過多,也會導致一筆不小的開銷。在C語言中,可以通過動態數組來解決這一問題。但是在一些場景中,用起來較為複雜。通過實際例子說明問題,聲明一個結構體中,其中包含一個數組成員。
  • 微信用久了,佔用很多內存怎麼辦?用這3個方法就能解決
    但是,大家用的時間長了,就會發現一個問題,微信佔用的內存越來越多,這不僅會影響到微信的運行速度,還會影響到其他app的運行速度,甚至手機也會變得越來越卡。那麼問題來了,到底是什麼原因造成它佔用的內存越來越大,有什麼方法可以解決嗎?
  • ...Win10 Chrome 惱人內存佔用和崩潰問題 啟用TerminateProcess
    外媒 Windows Latest 報導,隨著 Windows 10 版本 2004 的發布,微軟對作業系統中的 「Segment Heap」內存管理功能進行了改進,並增加了對 Web 瀏覽器等桌面(Win32)程序的支持。