【Rust】寫時複製Cow

寫時複製(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_mutinto_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,
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章