Kara za wydajność używania clone_from_slice () zamiast copy_from_slice ()?

Aug 15 2020

W Rust istnieją dwie metody aktualizowania zawartości wycinka z innego wycinka: clone_from_slice()i copy_from_slice(). Zachowanie tych dwóch funkcji nie jest zaskakujące - pierwsza klonuje i oczekuje typu do zaimplementowania Clone, a druga kopiuje i oczekuje, że typ zostanie zaimplementowany Copy.

Zaskakuje mnie jednak, że dokumentacja clone_from_slicemówi tak: „Jeśli Timplementuje Copy, może być bardziej wydajna w użyciu copy_from_slice”. Zaskakujące jest to, że tutaj powinna być różnica w wydajności. Jeśli Timplementuje Copy, .clone()to musi być równoważne z kopiowaniem bitów; jednak ponieważ kompilator wie, jaki Tjest typ , powinien być w stanie dowiedzieć się, czy może wykonać kopię bitową, nawet jeśli używam clone_from_slice.

Skąd więc bierze się nieefektywność wydajności?

Odpowiedzi

4 ÖmerErden Aug 15 2020 at 21:14

TL; DR Proszę sprawdzić źródło clone_from_slice , odwiedza wszystkie elementy plasterka i wywołuje clonekażdy z nich, podczas gdy copy_from_slice bezpośrednio kopiuje wszystkie bity z memcpy.


Jeśli T implementuje Copy, .clone()to musi być równoważne z kopiowaniem bitów

Nawet jeśli każdy Copytyp byłby implementowany Clonedomyślnie, gdzie clonebezpośrednio używacopy ; clone_from_slicebędzie nadal przechodzić przez wycinek i kopiować podczas przechodzenia.

Ale żadna ta propozycja nie jest poprawna dla prymitywów, ale nie jest poprawna w przypadkach takich jak poniżej :

#[derive(Copy)]
struct X;

impl Clone for X {
    fn clone(&self) -> Self {
        //do some heavy operation or light(depends on the logic)

        X
    }
}

Chociaż Clonemoże być zaimplementowany przez dowolny Copytyp logiki , po prostu kopiuje bity podczas duplikowania obiektu.

Jeśli implementuje T Copy, może być bardziej wydajne w użyciucopy_from_slice

Ważne jest tutaj, w dokumentacji jest napisane „ może być ” nie „ będzie ”, to daje takie możliwości jak

  • Cloneimplementacja może bezpośrednio korzystać z Copyimplementacji. W przypadku podstawowych typów, takich jak prymitywy, optymalizator może bezpośrednio użyć memcpyzamiast przechodzenia, wtedy moglibyśmy zaakceptować tę propozycję jako błędną, ponieważ jedna nie będzie wydajna od drugiej.

  • Cloneimplementacja może bezpośrednio korzystać z Copyimplementacji. W przypadku typów złożonych (kwestia trawersowania powyżej) ta propozycja jest poprawna. ( Edytowałem przykład z @kmdreko z nieco bardziej złożoną strukturą, sprawdź wynik z godbolt )

  • Cloneimplementacja jest niestandardowa i jest to Copytyp, ten sprawi, że ta propozycja będzie poprawna, nawet niestandardowa implementacja jest niedroga, wtedy copydla dużych wycinków użycie memcpymoże być bardziej korzystne.