W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
ch13-04-performance.md
commit 009fffa4580ffb175f1b8470b5b12e4a63d670e4
為了決定使用哪個(gè)實(shí)現(xiàn),我們需要知道哪個(gè)版本的 search
函數(shù)更快一些:是直接使用 for
循環(huán)的版本還是使用迭代器的版本。
我們運(yùn)行了一個(gè)性能測(cè)試,通過將阿瑟·柯南·道爾的“福爾摩斯探案集”的全部?jī)?nèi)容加載進(jìn) String
并尋找其中的單詞 “the”。如下是 for
循環(huán)版本和迭代器版本的 search
函數(shù)的性能測(cè)試結(jié)果:
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
結(jié)果迭代器版本還要稍微快一點(diǎn)!這里我們將不會(huì)查看性能測(cè)試的代碼,我們的目的并不是為了證明他們是完全等同的,而是得出一個(gè)怎樣比較這兩種實(shí)現(xiàn)方式性能的基本思路。
對(duì)于一個(gè)更全面的性能測(cè)試,將會(huì)檢查不同長(zhǎng)度的文本、不同的搜索單詞、不同長(zhǎng)度的單詞和所有其他的可變情況。這里所要表達(dá)的是:迭代器,作為一個(gè)高級(jí)的抽象,被編譯成了與手寫的底層代碼大體一致性能代碼。迭代器是 Rust 的 零成本抽象(zero-cost abstractions)之一,它意味著抽象并不會(huì)引入運(yùn)行時(shí)開銷,它與本賈尼·斯特勞斯特盧普(C++ 的設(shè)計(jì)和實(shí)現(xiàn)者)在 “Foundations of C++”(2012) 中所定義的 零開銷(zero-overhead)如出一轍:
In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.從整體來說,C++ 的實(shí)現(xiàn)遵循了零開銷原則:你不需要的,無需為他們買單。更有甚者的是:你需要的時(shí)候,也不可能找到其他更好的代碼了。
- Bjarne Stroustrup "Foundations of C++"
- 本賈尼·斯特勞斯特盧普 "Foundations of C++"
作為另一個(gè)例子,這里有一些取自于音頻解碼器的代碼。解碼算法使用線性預(yù)測(cè)數(shù)學(xué)運(yùn)算(linear prediction mathematical operation)來根據(jù)之前樣本的線性函數(shù)預(yù)測(cè)將來的值。這些代碼使用迭代器鏈來對(duì)作用域中的三個(gè)變量進(jìn)行了某種數(shù)學(xué)計(jì)算:一個(gè)叫 buffer
的數(shù)據(jù) slice、一個(gè)有 12 個(gè)元素的數(shù)組 coefficients
、和一個(gè)代表位移位數(shù)的 qlp_shift
。例子中聲明了這些變量但并沒有提供任何值;雖然這些代碼在其上下文之外沒有什么意義,不過仍是一個(gè)簡(jiǎn)明的現(xiàn)實(shí)中的例子,來展示 Rust 如何將高級(jí)概念轉(zhuǎn)換為底層代碼:
let buffer: &mut [i32];
let coefficients: [i64; 12];
let qlp_shift: i16;
for i in 12..buffer.len() {
let prediction = coefficients.iter()
.zip(&buffer[i - 12..i])
.map(|(&c, &s)| c * s as i64)
.sum::<i64>() >> qlp_shift;
let delta = buffer[i];
buffer[i] = prediction as i32 + delta;
}
為了計(jì)算 prediction
的值,這些代碼遍歷了 coefficients
中的 12 個(gè)值,使用 zip
方法將系數(shù)與 buffer
的前 12 個(gè)值組合在一起。接著將每一對(duì)值相乘,再將所有結(jié)果相加,然后將總和右移 qlp_shift
位。
像音頻解碼器這樣的程序通常最看重計(jì)算的性能。這里,我們創(chuàng)建了一個(gè)迭代器,使用了兩個(gè)適配器,接著消費(fèi)了其值。Rust 代碼將會(huì)被編譯為什么樣的匯編代碼呢?好吧,在編寫本書的這個(gè)時(shí)候,它被編譯成與手寫的相同的匯編代碼。遍歷 coefficients
的值完全用不到循環(huán):Rust 知道這里會(huì)迭代 12 次,所以它“展開”(unroll)了循環(huán)。展開是一種移除循環(huán)控制代碼的開銷并替換為每個(gè)迭代中的重復(fù)代碼的優(yōu)化。
所有的系數(shù)都被儲(chǔ)存在了寄存器中,這意味著訪問他們非常快。這里也沒有運(yùn)行時(shí)數(shù)組訪問邊界檢查。所有這些 Rust 能夠提供的優(yōu)化使得結(jié)果代碼極為高效?,F(xiàn)在知道這些了,請(qǐng)放心大膽的使用迭代器和閉包吧!他們使得代碼看起來更高級(jí),但并不為此引入運(yùn)行時(shí)性能損失。
閉包和迭代器是 Rust 受函數(shù)式編程語言觀念所啟發(fā)的功能。他們對(duì) Rust 以底層的性能來明確的表達(dá)高級(jí)概念的能力有很大貢獻(xiàn)。閉包和迭代器的實(shí)現(xiàn)達(dá)到了不影響運(yùn)行時(shí)性能的程度。這正是 Rust 竭力提供零成本抽象的目標(biāo)的一部分。
現(xiàn)在我們改進(jìn)了我們 I/O 項(xiàng)目的(代碼)表現(xiàn)力,讓我們看一看更多 ?cargo
? 的功能,他們將幫助我們準(zhǔn)備好將項(xiàng)目分享給世界。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: