尝试将Vec的内容复制到另一个Vec中,如何使用copy_from_slice()方法?

5
我正在尝试将一个Vec的内容复制到一个已存在的Vec中,以替换目标Vec的内容。
以下是我所期望的:
1. 不需要分配新内存(除非目标Vec太短),因为目标Vec已经被分配了。 2. 不应使用迭代器,因为memcopy足以完成工作。 3. 源Vec不应更改。 4. 最好使用安全方法。
以下是我尝试过的方法:
1. vec.clone():提供正确的内容,但会分配新内存,这对于将内容复制到已经存在且足够大的Vec中是不必要的。 2. vec.clear(); vec.extend():原地复制,但似乎在每个元素上使用了迭代器,这是不必要的,我只想要一个memcopy。 3. vec.copy_from_slice():正是我要找的,但需要完全相同大小的缓冲区,出于某种原因,我似乎无法获得相同大小的缓冲区。
以下是不起作用的内容。
#![feature(shrink_to)]
fn vec_copy(src: &Vec<i32>, dst: &mut Vec<i32>) {
    // Try to adjust dst buffer size... there should be a better way
    if src.len() > dst.len() {
        let addon = src.len() - dst.len();
        dst.reserve_exact(addon);
    } else {
        dst.shrink_to(src.len());
    }
    // Do the copy        
    // panics! :
    // thread 'main' panicked at libcore/slice/mod.rs:1645:9
    // 'destination and source slices have different lengths'
    // 
    dst.copy_from_slice(src.as_slice()); // <--- panics here
}

fn main() {
    // Copy from a shorter Vec
    let mut container = vec![1, 2];
    let test1 = vec![3]; // shorter vec
    println!("{:p} = {:?}", &container[0], container); // output: 0x7f00bda20008 = [1, 2]
    vec_copy(&test1, &mut container); // panics inside function 
    println!("{:p} = {:?}", &container[0], container); // expected: 0x7f00bda20008 = [3]

    // Copy from a longer Vec
    container = vec![1, 2];    
    let test2 = vec![4, 5, 6]; // longer Vec
    println!("{:p} = {:?}", &container[0], container); // output: 0x7fef5b820018 = [1, 2]
    vec_copy(&test2, &mut container); // panics inside function
    println!("{:p} = {:?}", &container[0], container); // expected: 0x7fef5b820018 = [4, 5, 6]    
}

错误引发的紧急情况:

thread 'main' panicked at libcore/slice/mod.rs:1645:9,
'destination and source slices have different lengths'

问题

使用 vec.copy_from_slice() 似乎是将一个 Vec 的内容原地 memcopy 到另一个 Vec 中,而不需要进行不必要的内存分配和使用迭代器的方法。

如何设置目标 Vec 的大小,以便 vec.copy_from_slice() 不会引发 panic?


1
它不能使用迭代器,因为使用 memcopy 应该已经足够完成工作了。这是基于假设迭代器会阻止使用 memcpy,但事实并非如此。 - Shepmaster
@Shepmaster 是的,我之前有一个先入为主的想法,认为迭代器(因为名称)会在循环内独立地复制每个元素,而不是执行一个大的memcpy。今天我学到了。 - Jocelyn
2个回答

4
你想使用 Vec::extend_from_slice
fn vec_copy(src: &[i32], dst: &mut Vec<i32>) {
    // Optionally truncate to zero if there might be existing data
    // dst.clear();
    dst.extend_from_slice(src);
}

这样做可以避免在添加比之前分配的元素更多时需要使用“虚拟值”填充向量。
如果你查看发布模式下的汇编,会在确认已分配足够空间后调用memcpy
playground::vec_copy:
    pushq   %r15
    pushq   %r14
    pushq   %r12
    pushq   %rbx
    subq    $88, %rsp
    movq    %rdx, %rbx
    movq    %rsi, %r15
    movq    %rdi, %r14
    movq    8(%rbx), %rsi
    movq    16(%rbx), %r12
    movq    %rsi, %rax
    subq    %r12, %rax
    cmpq    %r15, %rax
    jae .LBB1_14
    addq    %r15, %r12
    jb  .LBB1_8
    leaq    (%rsi,%rsi), %rax
    cmpq    %rax, %r12
    cmovbq  %rax, %r12
    movl    $4, %ecx
    movq    %r12, %rax
    mulq    %rcx
    jo  .LBB1_8
    testq   %rsi, %rsi
    je  .LBB1_9
    shlq    $2, %rsi
    movq    (%rbx), %rdi
    movq    %rsp, %r9
    movl    $4, %edx
    movl    $4, %r8d
    movq    %rax, %rcx
    callq   __rust_realloc@PLT
    testq   %rax, %rax
    jne .LBB1_5
    movq    (%rsp), %rax
    jmp .LBB1_12

.LBB1_9:
    movq    %rsp, %rdx
    movl    $4, %esi
    movq    %rax, %rdi
    callq   __rust_alloc@PLT
    testq   %rax, %rax
    je  .LBB1_12

.LBB1_5:
    xorl    %ecx, %ecx
    movdqa  32(%rsp), %xmm0
    movdqa  %xmm0, 48(%rsp)
    testq   %rcx, %rcx
    je  .LBB1_13

.LBB1_6:
    movq    %rax, (%rsp)
    movaps  48(%rsp), %xmm0
    movups  %xmm0, 8(%rsp)
    leaq    64(%rsp), %rdi
    movq    %rsp, %rsi
    callq   <core::heap::CollectionAllocErr as core::convert::From<core::heap::AllocErr>>::from@PLT
    movdqa  64(%rsp), %xmm0
    movq    %xmm0, %rax
    cmpq    $3, %rax
    je  .LBB1_14
    cmpq    $2, %rax
    jne .LBB1_15

.LBB1_8:
    leaq    .Lbyte_str.5(%rip), %rdi
    callq   core::panicking::panic@PLT
    ud2

.LBB1_12:
    movups  8(%rsp), %xmm0
    movaps  %xmm0, 32(%rsp)
    movl    $1, %ecx
    movdqa  32(%rsp), %xmm0
    movdqa  %xmm0, 48(%rsp)
    testq   %rcx, %rcx
    jne .LBB1_6

.LBB1_13:
    movq    %rax, (%rbx)
    movq    %r12, 8(%rbx)

.LBB1_14:
    movq    16(%rbx), %rdi
    leaq    (%rdi,%r15), %rax
    movq    %rax, 16(%rbx)
    shlq    $2, %r15
    shlq    $2, %rdi
    addq    (%rbx), %rdi
    movq    %r14, %rsi
    movq    %r15, %rdx
    callq   memcpy@PLT
    addq    $88, %rsp
    popq    %rbx
    popq    %r12
    popq    %r14
    popq    %r15
    retq

.LBB1_15:
    movq    80(%rsp), %rax
    movdqa  %xmm0, (%rsp)
    movq    %rax, 16(%rsp)
    movq    %rsp, %rdi
    callq   <alloc::heap::Heap as core::heap::Alloc>::oom
    ud2

println!("{:p} = {:?}", &container[0], container);

container.as_ptr()&container[0]更明显,并且在向量为空时不会失败。

另请参阅:


感谢各种技巧!我曾担心extend_from_slice()会在循环内独立复制每个元素。我被文档中的描述所困惑,它声明_"遍历切片other,克隆每个元素,然后将其附加到此Vec"_。但实际上它内部调用了copy_from_slice()并执行唯一的memcpy,这正是我要寻找的。在复制之前,它会执行reserve()set_len(),这就是为什么我们可以在之前使用truncate()的原因。 - Jocelyn
1
请注意,vec.truncate(0) 可以缩写为 vec.clear() - Jocelyn

2
您可以使用Vec.resize()函数:
fn vec_copy(src: &Vec<i32>, dst: &mut Vec<i32>) {
    dst.resize(src.len(), 0);
    dst.copy_from_slice(src.as_slice());
}

fn main() {
    // Copy from a shorter Vec
    let mut container = vec![1, 2];
    let test1 = vec![3]; // shorter vec
    println!("{:p} = {:?}", &container[0], container); // output: 0x7f00bda20008 = [1, 2]
    vec_copy(&test1, &mut container); // panics inside function 
    println!("{:p} = {:?}", &container[0], container); // expected: 0x7f00bda20008 = [3]

    // Copy from a longer Vec
    container = vec![1, 2];    
    let test2 = vec![4, 5, 6]; // longer Vec
    println!("{:p} = {:?}", &container[0], container); // output: 0x7fef5b820018 = [1, 2]
    vec_copy(&test2, &mut container); // panics inside function
    println!("{:p} = {:?}", &container[0], container); // expected: 0x7fef5b820018 = [4, 5, 6]    
}

输出:

0x7f2e36020008 = [1, 2]
0x7f2e36020008 = [3]
0x7f2e36020028 = [1, 2]
0x7f2e3602a010 = [4, 5, 6]

playground

最后一个地址不同,可能是因为目标容量比源容量小,所以需要扩容,即重新分配内存。可以通过打印目标容量并观察其从2增长到4来证明这一点:

println!("{:p} = {:?} (capacity: {:?})", &container[0], container, container.capacity());
vec_copy(&test2, &mut container); // panics inside function
println!("{:p} = {:?} (capacity: {:?})", &container[0], container, container.capacity());

输出:

0x7f16a1820008 = [1, 2]
0x7f16a1820008 = [3]
0x7f16a1820028 = [1, 2] (capacity: 2)
0x7f16a182a010 = [4, 5, 6] (capacity: 4)

您可以通过使用Vec::with_capacity来确保在创建时目标足够大,从而防止这种情况发生。此外,playground是一个很好的在线Rust代码编辑器和调试器。

谢谢!是的,当目标较小时由于realloc地址会有所不同,我不知道为什么错过了这一点。您的解决方案可行,最终我接受了@Shipmaster的答案,因为它不需要在realloc的情况下用虚拟值填充目标(这里的resize()会这样做),并且仍然在内部使用copy_from_slice()。 - Jocelyn
1
没问题,我理解并同意。很高兴能帮忙! - Jorge Israel Peña

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接