¿Penalización de rendimiento por usar clone_from_slice() en lugar de copy_from_slice()?

Aug 15 2020

En Rust, hay dos métodos para actualizar el contenido de un segmento desde otro segmento: clone_from_slice()y copy_from_slice(). El comportamiento de estas dos funciones no sorprende: la primera hace una clonación y espera que el tipo la implemente Clone, mientras que la segunda hace una copia y espera que el tipo la implemente Copy.

Sin embargo, me sorprende que la documentación de clone_from_slicedice esto: "If Timplements Copy, puede ser más eficaz de usar copy_from_slice". Es sorprendente que debería haber una diferencia de rendimiento aquí. Si Timplementa Copy, entonces .clone()se requiere que sea equivalente a copiar bits; sin embargo, dado que el compilador sabe qué tipo Tes, debería poder averiguar si puede hacer una copia bit a bit incluso si uso clone_from_slice.

Entonces, ¿de dónde surge la ineficiencia en el desempeño?

Respuestas

4 ÖmerErden Aug 15 2020 at 21:14

TL; DR Verifique la fuente de clone_from_slice , está visitando todos los elementos de slice y llamando clonea cada uno, mientras que copy_from_slice copia directamente todos los bits con memcpy.


Si T implementa Copy, entonces .clone()se requiere que sea equivalente a copiar bits

Incluso si cada Copytipo se implementara Clonede forma predeterminada donde clonese usa directamente elcopy ; clone_from_sliceseguirá recorriendo el corte y haciendo la copia mientras lo recorre.

Pero no, esta proposición es correcta para las primitivas pero no para los casos como los siguientes :

#[derive(Copy)]
struct X;

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

        X
    }
}

Si bien Clonepuede implementarse mediante cualquier Copytipo de lógica, simplemente copiará bits al duplicar un objeto.

Si T implementa Copy, puede ser más eficiente usarlocopy_from_slice

Lo importante está aquí, la documentación dice " puede ser " no " será ", esto trae posibilidades como

  • CloneLa implementación puede usar directamente la Copyimplementación. Para los tipos básicos como los primitivos, el optimizador puede usar directamente memcpyen lugar de atravesar, entonces podemos aceptar esta proposición como incorrecta porque uno no tendrá el mismo rendimiento que el otro.

  • CloneLa implementación puede usar directamente la Copyimplementación. Para tipos complejos (el problema de atravesar anterior) hace que esta proposición sea correcta. (He editado el ejemplo de @kmdreko con una estructura un poco más compleja, verifique el resultado de Godbolt )

  • Clonela implementación es personalizada y es un Copytipo, este hará que esta propuesta sea correcta, incluso la implementación personalizada es económica, entonces copypara las porciones grandes, el uso memcpypodría ser más beneficioso.