Kara za wydajność używania clone_from_slice () zamiast copy_from_slice ()?
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_slice
mówi tak: „Jeśli T
implementuje 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 T
implementuje Copy
, .clone()
to musi być równoważne z kopiowaniem bitów; jednak ponieważ kompilator wie, jaki T
jest 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
TL; DR Proszę sprawdzić źródło clone_from_slice , odwiedza wszystkie elementy plasterka i wywołuje clone
każ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 Copy
typ byłby implementowany Clone
domyślnie, gdzie clone
bezpośrednio używacopy
; clone_from_slice
bę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ż Clone
może być zaimplementowany przez dowolny Copy
typ 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
Clone
implementacja może bezpośrednio korzystać zCopy
implementacji. W przypadku podstawowych typów, takich jak prymitywy, optymalizator może bezpośrednio użyćmemcpy
zamiast przechodzenia, wtedy moglibyśmy zaakceptować tę propozycję jako błędną, ponieważ jedna nie będzie wydajna od drugiej.Clone
implementacja może bezpośrednio korzystać zCopy
implementacji. 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 )Clone
implementacja jest niestandardowa i jest toCopy
typ, ten sprawi, że ta propozycja będzie poprawna, nawet niestandardowa implementacja jest niedroga, wtedycopy
dla dużych wycinków użyciememcpy
może być bardziej korzystne.