假設我有以下內容:
fn into_three_tuple(mut v: Vec<String>) -> (String, String, String) {
if v.len() == 3 {
// ???
} else {
panic!()
}
}
我應該用什么來替換???以獲得最佳性能?
可能的解決方案
當然,我可以
...
if v.len() == 3 {
let mut iter = v.into_iter();
(v.next().unwrap(), v.next().unwrap(), v.next().unwrap())
} else {
...
或類似
if v.len() == 3 {
let mut iter = v.into_iter();
let e2 = v.pop.unwrap();
let e1 = v.pop.unwrap();
let e0 = v.pop.unwrap();
(e0, e1, e2)
} else {
...
這些解決方案的問題
這兩個實作都使用unwrap,如果我理解正確,它會執行運行時檢查。但是既然我們v.len() == 3有條件,我們知道向量保證有 3 個元素,所以運行時檢查是不必要的。
此外,該into_iter解決方案可能會引入創建迭代器的額外開銷,并且該pop解決方案可能會引入減少v內部len欄位的額外開銷,這看起來很愚蠢,因為v在提取元素后會立即洗掉(所以我們不關心是否它的len欄位是準確的)。
題
是否有一些(可能unsafe)更有效的方法(例如,某種直接獲取任意索引元素所有權的方法)?
或者編譯器已經足夠聰明,可以跳過這些無關的操作?
還是我只需要忍受次優的性能?
筆記
In case you're wondering why I'm obsessing over such a tiny micro-optimization, I'm writing a fairly speed-critical application, and this function will be called a significant amount of times.
uj5u.com熱心網友回復:
標準庫中提供了一種從向量中提取固定數量項的操作:將其轉換為陣列。
use std::convert::TryInto; // needed only if not using 2021 edition / Rust 1.55 or earlier
pub fn into_three_tuple(v: Vec<String>) -> (String, String, String) {
let three_array: [String; 3] = v.try_into().unwrap();
let [a, b, c] = three_array;
(a, b, c)
}
我不太熟悉閱讀 x86 程式集,但這確實可以編譯為具有較少分支的更簡單的代碼。我通常認為這是在大多數情況下解包三元素向量的最快方法;如果它確實更慢,那么這將是標準庫中的一個性能錯誤,應該報告和修復。
您還應該考慮[String; 3]在程式的其余部分使用陣列而不是元組。該型別撰寫起來更短,它們允許您使用陣列和切片操作來處理所有三個字串。此外,元組沒有保證的記憶體布局,而陣列有(盡管在這種情況下它們實際上可能是相同的)。
將回傳型別更改為陣列會使函式變得簡單——可能對型別宣告有用,但不包含任何有趣的代碼:
pub fn into_three_array(v: Vec<String>) -> [String; 3] {
v.try_into().unwrap()
}
uj5u.com熱心網友回復:
免責宣告:正如評論中所提到的,您應該進行基準測驗以檢查其中的任何一個實際上是否對您的程式產生影響。您使用了許多 3 元素向量(它們是堆分配的,因此效率相對較低)這一事實表明您可能過度優化,或者在錯誤的地方進行了優化。話說回來...
該
into_iter解決方案可能會引入創建迭代器的額外開銷
請注意,“迭代器”是一個對編譯器完全透明的微小堆疊上的值,它可以繼續完全行內/消除它。
或者編譯器已經足夠聰明,可以跳過這些無關的操作?
在許多情況下,檢查v.len() == <concrete number>確實足以讓編譯器省略邊界檢查,因為它有向量大小的證明。但是,這似乎不適用于您嘗試過的方法。將代碼修改為std::process::exit()if v.len() != 3so 唯一的恐慌來自運行時檢查后,運行時檢查(作為呼叫恐慌的證據)仍然沒有通過.pop()或into_iter()方法洗掉。
是否有一些(可能不安全的)更有效的方式(例如,某種直接獲取任意索引元素所有權的方式)?
是的。一種方法是用來unreachable_unchecked()避免恐慌,我們可以證明呼叫next()會成功:
use std::hint::unreachable_unchecked;
pub fn into_three_tuple(v: Vec<String>) -> (String, String, String) {
if v.len() == 3 {
let mut v = v.into_iter();
unsafe {
let e0 = v.next().unwrap_or_else(|| unreachable_unchecked());
let e1 = v.next().unwrap_or_else(|| unreachable_unchecked());
let e2 = v.next().unwrap_or_else(|| unreachable_unchecked());
(e0, e1, e2)
}
} else {
panic!()
}
}
以與上述相同的方式修改代碼顯示沒有與恐慌相關的代碼。
不過,這取決于編譯器是否足夠智能。如果您想確保不進行邊界檢查,Rust unsafe 也允許您這樣做。您可以使用as_ptr()獲取指向存盤在向量中的元素的原始指標,并直接從那里讀取它們。您需要呼叫set_len()以防止向量洗掉您移動的元素,但仍允許它釋放存盤空間。
pub fn into_three_tuple(mut v: Vec<String>) -> (String, String, String) {
if v.len() == 3 {
unsafe {
v.set_len(0);
let ptr = v.as_ptr();
let e0 = ptr.read();
let e1 = ptr.add(1).read();
let e2 = ptr.add(2).read();
(e0, e1, e2)
}
} else {
panic!("expected Vec of length 3")
}
}
The generated code again shows no bound check related panics, which is expected because there are no calls to functions that performs a checked access to data.
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/337182.html
標籤:performance rust
上一篇:如何停止Accordion內AccordionBody組件的遞回渲染?
下一篇:SpringSecurityBCryptPasswordEncoder:編碼后的密碼看起來不像使用相同密碼的BCrypt
