在golang中经常会踩一个坑,那就是slice append,golang的动态数组也称为slice,使用append可以对动态数组进行添加元素,但是slice空间不够之后golang会自动重新分配内存空间,每次重新分配的内存空间是原空间的2倍,而且有个更坑的是,golang中slice每次重新分配内存都是重新分配一片 2N 大小的内存,然后把原来的数据拷贝过去,这样一来,性能损耗更大了。
那么rust中的数组是怎么处理动态增长的呢,我们来一段代码测试一下。
fn main() {
let mut vec = Vec::with_capacity(100);
// The vector contains no items, even though it has capacity for more
println!("vec.len: {:?}", vec.len());
println!("vec.cap: {:?}", vec.capacity());
// These are all done without reallocating...
for i in 0..100 {
vec.push(i);
}
println!("vec.len: {:?}", vec.len());
println!("vec.cap: {:?}", vec.capacity());
// ...but this may make the vector reallocate
vec.push(101);
println!("vec.len: {:?}", vec.len());
println!("vec.cap: {:?}", vec.capacity());
}
输出如下
vec.len: 0
vec.cap: 100
vec.len: 100
vec.cap: 100
vec.len: 101
vec.cap: 200
可以看到,rust中,当vector数组存储满了之后,再往里面添加元素,vector就会重新分配内存,新分配的内存也是原来空间的2倍,但是,他是在原来的内存上扩充的,而不是像golang一样重新分配一片2N的内存空间替换旧的内存。性能损耗上,相比golang少了copy数据和释放旧空间。所以在高性能场景下,这里依然不建议使用vector的自动增长特性,自动增长的内存分配会消耗存储空间,而且 2N 的增长步长会很容易导致内存泄漏,你如果依赖这个自动增长特性,你将会发现你使用的内存可能会发生 2^n 指数级增长。这绝对会在大部分边界条件下导致你的程序迅速的发生OOM。
远离动态数组,远离bug。