Rust程式語言融合了多種程式設計法(programming paradigm),其中的指令式程式設計(imperative programming)所用的迴圈和函數式程式設計(functional programming)所提供的迭代器(iterator)可以加強陣列或是切片的走訪效能。然而,很多時候我們並不需要從頭走訪陣列或是切片,這時可以利用Rust提供的陣列轉切片或是切片再切片的功能來進行索引值的位移,事實上迭代器也有提供skip
方法,可以在跳過從前面算起的指定次數的迭代處理程序後,後面的迭代程序才會真正地開始被處理。那如果迭代器的走訪對象是陣列或是切片時,使用skip
方法會比直接使用更小範圍的切片還要慢嗎?
我們先來看看以下程式碼:
let array = [2, 2, 3, 4, 5, 6, 7, 8];
for i in 5..array.len() {
println!("{}", array[i]);
}
以上程式,會利用For計次迴圈來從索引值5
開始走訪array
這個存在於堆疊內的陣列,並將走訪到的元素值印在螢幕上。
在先前的文章中,我們已經知道用For迴圈來走訪堆疊內的陣列,效能跟和用For迭代器迴圈或是迭代器是一樣的。
所以,以上程式其實也會等效於以下的For迭代器迴圈程式:
let array = [2, 2, 3, 4, 5, 6, 7, 8];
for &n in array[5..].iter() {
println!("{}", n);
}
以上程式,利用Rust提供的陣列轉切片功能,直接從陣列的索引值5
開始走訪陣列。那如果我們不使用切片,而是用迭代器提供的skip
方法來完成這個在走訪陣列時略過前面幾個元素的功能呢?程式如下:
let array = [2, 2, 3, 4, 5, 6, 7, 8];
for &n in array.skip(5) {
println!("{}", n);
}
使用skip
方法所二度產生出來的迭代器,會影響到效能嗎?
效能實測
直接實際寫一段程式來測試運算效能吧!這段程式可以在GitHub上取得:
根據測試結果,可以發現使用skip
方法的效能明顯比使用切片還要差。這是因為skip
方法所產生的Skip
結構實體,在第一次迭代被呼叫next
方法時,因為n
在經過if
條件式判斷之後會發現不等於0,所以會再去呼叫nth
方法,程式碼如下:
fn next(&mut self) -> Option<I::Item> {
if self.n == 0 {
self.iter.next()
} else {
let old_n = self.n;
self.n = 0;
self.iter.nth(old_n)
}
}
而這個迭代器的nth
方法究竟會做什麼事,還得看迭代器本身的實作方式。預設的nth
方法實作方式是一邊走訪一邊略過指定的元素數量,所以雖然看起來陣列前面的元素並沒有進入迭代程序,但其實還是有被走訪到!nth
方法的程式碼如下:
fn nth(&mut self, mut n: usize) -> Option<Self::Item> {
for x in self {
if n == 0 { return Some(x) }
n -= 1;
}
None
}
不過如果是切片產生出來的迭代器,nth
方法有著它專屬的實作方式,程式碼如下:
fn nth(&mut self, n: usize) -> Option<$elem> {
if n >= len!(self) {
if mem::size_of::<T>() == 0 {
self.end = self.ptr;
} else {
self.ptr = self.end;
}
return None;
}
unsafe {
let elem = Some(& $( $mut_ )* *self.ptr.add(n));
self.post_inc_start((n as isize).wrapping_add(1));
elem
}
}
以上其實是寫在巨集中的程式,我們可以看出Rust的切片是直接利用指標來進行記憶體位址的位移。這樣的方式其實還算不錯,但實際運行起來的效能卻慘輸另外兩種方式,或許跟編譯器優化有關,不曉得。總之,skip
方法還是儘量少用為妙。