2011年9月29日

STL vector 效率小記

懶人看結論

用 C++ 寫程式,會用到 vector 來儲存陣列。用過的人都知道,用法大約是

vector<string> foo;
foo.push_back("Hello");
foo.push_back("World");

要拿單筆資料時,可以用 foo[1] 拿到 string("World")。要 loop 過全部資料時,寫

for (vector<string>::iterator it = foo.begin(); it != foo.end(); ++it) {
  //  *it 是字串資料
}
如果只要讀資料不改資料,可以用 const_iterator 代替 iterator。

用 push_back() 新增資料的一個特性是:如果 vector 的容量不夠,會自動配置新的記憶體空間(預設是兩倍大)、把資料拷過去,再把新資料寫入。

記憶體配置是很耗時的,能免則免。如果事先不知道所需的儲存量,這樣依需要配空間做是維持 vector 用 O(1) 時間取得資料、又不會浪費太多空間的好方法。可是如果事先知道要存 65 筆資料,還反覆去要 4、8、16、32、64 筆的空間,最後又用 128 筆的空間來存 65 筆,既浪費時間、又浪費空間!

所以 vector class 提供了三種指定長度的方法:reserve()、resize() 和 constructor 引數:

vector<string> a;  // 長度為 0
a.reserve(65);  // 預留 65 個元素的位置,沒有初始化
vector<string> b;  // 長度為 0
b.resize(65);  // 預留 65 個元素的位置,呼叫 string 的 default constructor
vector<string> c(65);  // 長度為 65

用在空的 vector 上,對慣 C 的人來說,reserve() 類似 malloc(),resize() 類似 calloc()。如果是在 constructor 給長度就有點像 char* c[65]; 這樣的陣列宣告。不過 C++ 和 C 還是不一樣,這邊的主要差異是物件,和物件的 default constructor、assignment operator 的行為,對效率有些影響。

先講講這三種做法的原理。

預留 reserve()

每個 vector 都有兩個重要的數字:容量(capacity)和長度(size)。容量是這個 vector 擁有的空間,長度是實際儲存了元素的空間大小,其值等於陣列的終點減起點。capacity 不小於 size 是個不變條件。

reserve() 的目的是擴大容量,做完時,vector 的長度不變,capacity 只會長大不會縮小,資料所在位置可能會移動。

reserve(N) 的做法很簡單:

  1. 如果 N <= 容量,不做事結束。
  2. 配置大小為 N 的空間,
  3. 把資料拷過去,
  4. 歸還原來的儲存空間,
  5. 更新陣列的起點、終點、容量。

因為 vector 一開始是空的,立刻預留顯然比填了資料後才預留省了拷資料的時間。

調整長度 resize()

調整長度目的是改變 vector 的長度,如果變小就擦掉尾巴的資料,如果變大就補零。更精確一點說,擦尾時會呼叫元素物件的 destructor,補零是補元素類型的 default constructor 產生的物件。補零如果會超過容量,會先預留空間,就是上面說的〔配置新空間、拷資料、歸還舊空間、更新陣列位置〕整個程序走一遍。

resize() 結束時,vector 的長度會改變為指定的大小,capacity 則視需要調整,確保不小於 size,資料所在位置可能會移動。

resize() 除了新長度 N 之外,還可以接受第二個引數,就是新元素的值,如果沒給就會用 default constructor 生一個來用。

在 constructor 指定長度

在 constructor 指定長度 N 的結果是一個容量和長度均為 N 的 vector,因為長度為 N,代表 N 個元素有資料,這資料就是元素類型的 default constructor 生出來的,或是用 vector constructor 的第二個引數給的物件。

這個 constructor 的做法也很簡單:

  1. 配置大小為 N 的空間,
  2. 設定陣列的起點、終點、容量,
  3. 把相同的值填入 N 個元素的位置。

效率

使用 vector 要注意的效率問題大致有兩點:
  1. 不做不必要的記憶體重配置:少依賴 push_back() 的自動記憶體配置,能自己要記憶體的就自己要,善用 reserve()、resize() 或 constructor 引數。
  2. 不做不必要的物件拷貝。比方說,要延長或建立一個 vector、但各個元素的值不同,不要用會填值的 resize() 或 constructor 引數,而是用 reserve() 再把物件一個一個 push_back() 進去。另外要注意的是,reserve() 要來的空間切不可用 operator[] 填值,除非元素是 POD。見下節。

少用 operator[]

既然寫了 vector,順便提一下 operator[]。一旦有個 vector<string> foo,可能會想用
foo[3] = "movie";
這種寫法,但要小心,這可能會 Segmentation Fault!
  • 第一,operator[] 不做邊界檢查,foo vector 的容量可能不到 4,foo[3] 就越界了。
  • 第二,foo vector 就算容量夠,但長度可能不到 4,foo[3] 是未初始化的記憶體,拿來當成 string 物件,就爆了,更不用說還要呼叫它的 assignment operator。但如果元素類別是 int、double、pointer 這類 POD(Plain Old Data),assignment 是用 memory copy 實作的,倒是不會出問題。

如果確定 vector 的各個位置都有物件了,但不確定 index 會不會越界(也許 index 是別人傳來的),除了自己檢查邊界外,也可以考慮用 at() 方法:

string that = foo.at(an_index);
foo.at(an_index) = "movie";
at() 會做邊界檢查,越界時會丟出 out_of_range 異常,比較容易 debug,必要時也可以把這個異常接住來處理。


2011年9月28日

重溫 Alice Cooper 的 Poison

前幾天在網路收音機上聽到 Alice Cooper 的 Poison,讓我一下回憶湧現...

台灣唱歌的好像一定要很純情或很傷情,這種描述慾念深到骨子裡的重搖滾,在台灣歌壇真的不容易聽到。忍不住把歌詞亂譯了,野人獻曝一下。

Your cruel device
Your blood like ice
One look could kill
My pain, your thrill

I wanna love you but I better not touch (Don't touch)
I wanna hold you but my senses tell me to stop
I wanna kiss you but I want it too much (Too much)
I wanna taste you but your lips are venomous poison

you're poison running through my veins
you're poison
I don't want to break these chains

Your mouth, so hot
Your web, I'm caught
Your skin, so wet
Black lace on sweat

I hear you calling and it's needles and pins (And pins)
I want to hurt you just to hear you screaming my name
Don't want to touch you but you're under my skin (Deep in)
I want to kiss you but your lips are venomous poison

你手段 殘酷
你冷血 如冰
看一眼 能殺人
我痛苦 你興奮

想要愛你 但我最好別碰
想要抱你 但理智叫我住手
想要親你 但我快忍不住了
想要嚐你 但你的唇是毒藥

你是流過我全身血脈的毒藥
你是毒藥
我不想掙脫這些鎖鍊

你的嘴 熾熱
你的網 捕我
你的膚 溼潤
黑蕾絲 香汗

聽你呼喚我 像是針在刺
想要傷害你 只為聽你喊我的名
不願撫摸你 但你已深入我身
好想要親你 但你的唇是毒藥

翻來覆去,好想、可是不該,再想、仍是不敢,連傷人的想法都出現了、最後還是退縮,完全是宅男碰到辣妹的心情啊!

拜網路之賜,現在不但可以在 YouTube 上找到原曲,Wikipedia 上列出的各種版本也找了不少。有這麼多不同流派的唱法,而且都還不錯,列出來方便以後回來聽。  :)


一、Alice Cooper 1989 年硬式搖滾原曲。

詞很內縮,曲很外放,配樂節奏就是爽快!

(對了,外套下好酷的 T-shirt!)








二、德國 Groove Coverage 2004 年的電子音樂版本。

不知道是怎樣的音樂環境,會讓這個團為這首曲子做了 21 種混音版?





三、澳洲〔外太空來的吸血僵屍〕 (Bloodsucking Zombies from Outer Space) 2005 年的版本。

之前沒聽過這個團,團名有夠 kuso。曲子前面聽起來和 Alice Cooper 的版本沒太大差別,副歌一進來我就笑得跌倒了!  XD




四、芬蘭女歌手 Tarja Turunen 2007 年的交響重金屬版本。

第一次聽到女主唱的交響重金屬,熱血啊!雖然她的音色太美太婉約了點,少了些重金屬橫衝直撞的感覺,但還是很想找她這張專輯來聽。





五、美國〔乾草種子迪西〕 (Hayseed Dixie) 2007 年的藍草版本。

實在太歡樂了!馬上想找身邊的女生勾起手來跳圈圈舞。  :)



六、德國的 DJ Gollum 在 2010 年混音的版本。

魔戒裡的咕嚕也來當 DJ 啦? XD    德國人好像真的很愛玩電子音樂!



七、2011 年 2 月福斯在以色列的 Passat 廣告

無人能敵的男主角哼唱的就是 Poison 的旋律配上自己的歌詞,最後當然要搞笑一下。



註:Poison 的官方 MV 有兩個版本,正常版的在 YouTube 上都是 240p 的,畫質太差不想貼;一刀未剪版的清楚很多、聲音也優,可是因為某種原因我不能貼,有慧根的人自己去找吧!  XDDD