寫時複製(Copy on Write)技術是一種程序中的優化策略,多應用於讀多寫少的場景。主要思想是創建對象的時候不立即進行復制,而是先引用(借用)原有對象進行大量的讀操作,只有進行到少量的寫操作的時候,才進行復制操作,將原有對象複製後再寫入。這樣的好處是在讀多寫少的場景下,減少了複製操作,提高了性能。
Rust中對應這種思想的是智能指針Cow<T>
,定義如下:
pub enum Cow<'a, B>
where
B: 'a + ToOwned + 'a + ?Sized,
{
Borrowed(&'a B), //用於包裹引用
Owned(<B as ToOwned>::Owned), //用於包裹所有者
}
可以看到是一個枚舉體,包括兩個可選值,一個是“借用”,一個是“所有”。具體含義是:以不可變的方式訪問借用內容,在需要可變借用或所有權的時候再克隆一份數據。
下面舉個例子說明Cow<T>
的應用:
use std::borrow::Cow;
fn abs_all(input: &mut Cow<[i32]>) {
for i in 0..input.len() {
let v = input[i];
if v < 0 {
input.to_mut()[i] = -v;
}
}
println!("value: {:?}", input);
}
fn main() {
// 只讀,不寫,沒有發生複製操作
let a = [0, 1, 2];
let mut input = Cow::from(&a[..]);
abs_all(&mut input);
assert_eq!(input, Cow::Borrowed(a.as_ref()));
// 寫時複製, 在讀到-1的時候發生複製
let b = [0, -1, -2];
let mut input = Cow::from(&b[..]);
abs_all(&mut input);
assert_eq!(input, Cow::Owned(vec![0,1,2]) as Cow<[i32]>);
// 沒有寫時複製,因爲已經擁有所有權
let mut input = Cow::from(vec![0, -1, -2]);
abs_all(&mut input);
assert_eq!(input, Cow::Owned(vec![0,1,2]) as Cow<[i32]>);
let v = input.into_owned();
assert_eq!(v, [0, 1, 2]);
}
上面這個用例已經講明瞭Cow<T>
的使用,下面我們繼續探索一下Cow<T>
的實現細節。重點關注to_mut
及into_owned
的實現。
to_mut
:就是返回數據的可變引用,如果沒有數據的所有權,則複製擁有後再返回可變引用;into_owned
:獲取一個擁有所有權的對象(區別與引用),如果當前是借用,則發生複製,創建新的所有權對象,如果已擁有所有權,則轉移至新對象。
impl<B: ?Sized + ToOwned> Cow<'_, B> {
#[stable(feature = "rust1", since = "1.0.0")]
pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {
// 如果時借用,則進行復制;如果已擁有所有權,則無需進行復制
match *self {
Borrowed(borrowed) => {
*self = Owned(borrowed.to_owned());
match *self {
Borrowed(..) => unreachable!(),
Owned(ref mut owned) => owned,
}
}
Owned(ref mut owned) => owned, //這裏解釋了上個例子中,已擁有所有權的情況,無需再複製
}
}
#[stable(feature = "rust1", since = "1.0.0")]
pub fn into_owned(self) -> <B as ToOwned>::Owned {
// 如果當前是借用,則發生複製,創建新的所有權對象,如果已擁有所有權,則轉移至新對象。
match self {
Borrowed(borrowed) => borrowed.to_owned(),
Owned(owned) => owned,
}
}
}