Rustで高速にsliceの一部をCloneする

TL;DR

Rustで配列をコピーしたいとき、実際問題何をするのが早いのかという話です。 結論だけ書いておくと、clone_from_sliceを使えばいいはずです。

clone_from_sliceの実装を読む

clone_from_slice

実装は以下です。spec_clone_fromを読んでいるだけです。

pub fn clone_from_slice(&mut self, src: &[T])
where
    T: Clone,
{
    self.spec_clone_from(src);
}

spec_clone_from

spec_clone_fromが何者なのかという話ですが、Tのオーバロード的なことをしている関数です。オーバロードっぽいことはtraitを使うことで実現できます(参考)。

T: Cloneの場合は普通にcloneして、T: Copyの場合はcopy_from_sliceを呼びます。

source

trait CloneFromSpec<T> {
    fn spec_clone_from(&mut self, src: &[T]);
}

impl<T> CloneFromSpec<T> for [T]
where
    T: Clone,
{
    #[track_caller]
    default fn spec_clone_from(&mut self, src: &[T]) {
        assert!(
            self.len() == src.len(),
            "destination and source slices have different lengths"
        );
        // NOTE: We need to explicitly slice them to the same length
        // to make it easier for the optimizer to elide bounds checking.
        // But since it can't be relied on we also have an explicit specialization for T: Copy.
        let len = self.len();
        let src = &src[..len];
        for i in 0..len {
            self[i].clone_from(&src[i]);
        }
    }
}

impl<T> CloneFromSpec<T> for [T]
where
    T: Copy,
{
    #[track_caller]
    fn spec_clone_from(&mut self, src: &[T]) {
        self.copy_from_slice(src);
    }
}

copy_from_slice

ptr::copy_nonoverlappingを呼びます。そのためのsafetyの確認も行われています。ptr::copy_nonoverlappingはだいたいmemcpyなので、Copy traitが必要です。

source

pub fn copy_from_slice(&mut self, src: &[T])
where
    T: Copy,
{
    // The panic code path was put into a cold function to not bloat the
    // call site.
    #[inline(never)]
    #[cold]
    #[track_caller]
    fn len_mismatch_fail(dst_len: usize, src_len: usize) -> ! {
        panic!(
            "source slice length ({}) does not match destination slice length ({})",
            src_len, dst_len,
        );
    }

    if self.len() != src.len() {
        len_mismatch_fail(self.len(), src.len());
    }

    // SAFETY: `self` is valid for `self.len()` elements by definition, and `src` was
    // checked to have the same length. The slices cannot overlap because
    // mutable references are exclusive.
    unsafe {
        ptr::copy_nonoverlapping(src.as_ptr(), self.as_mut_ptr(), self.len());
    }
}

結論

ということで、clone_from_sliceを呼べばよしなにやってくれるらしいです。 明示的にやりたい場合は、ptr::copy_nonoverlappingでもいいですが、unsafeは可能なら呼びたくないので、copy_from_sliceを呼ぶのがいいと思います。

この記事に関するIssueをGithubで作成する

Read Next