¿Por qué Vec::with_capacity es más lento que Vec::new para longitudes finales pequeñas?
Considere este código.
type Int = i32;
const MAX_NUMBER: Int = 1_000_000;
fn main() {
let result1 = with_new();
let result2 = with_capacity();
assert_eq!(result1, result2)
}
fn with_new() -> Vec<int> {
let mut result = Vec::new();
for i in 0..MAX_NUMBER {
result.push(i);
}
result
}
fn with_capacity() -> Vec<int> {
let mut result = Vec::with_capacity(MAX_NUMBER as usize);
for i in 0..MAX_NUMBER {
result.push(i);
}
result
}
Ambas funciones producen la misma salida. Una utiliza Vec::new
, la otra utiliza Vec::with_capacity
. Para valores pequeños de MAX_NUMBER
(como en el ejemplo), with_capacity
es más lento que new
. Solo para longitudes finales más grandes (por ejemplo, 100 millones) la versión que utiliza with_capacity
es tan rápida como la que utiliza new
.
Flamegraph para 1 millón de elementos
1 millón
Flamegraph para 100 millones de elementos
100 millones
Mi comprensión es que with_capacity
siempre debería ser más rápido si se conoce la longitud final, porque los datos en el montón se asignan una vez, lo que debería resultar en un solo bloque. En contraste, la versión con new
hace crecer el vector MAX_NUMBER
veces, lo que resulta en más asignaciones.
¿Qué me estoy perdiendo?
Editar
La primera sección se compiló con el perfil debug
. Si utilizo el perfil release
con la siguiente configuración en Cargo.toml
[package]
name = "vec_test"
version = "0.1.0"
edition = "2021"
[profile.release]
opt-level = 3
debug = 2
Todavía obtengo el siguiente resultado para una longitud de 10 millones.
davy.ai
La diferencia de rendimiento entre
Vec::new
yVec::with_capacity
puede depender de varios factores, incluyendo el hardware y el sistema operativo específicos que se estén utilizando, el tamaño del vector y el nivel de optimización del compilador Rust.En general,
Vec::with_capacity
debería ser más rápido queVec::new
cuando la longitud final del vector se conoce de antemano, ya que asigna el espacio necesario para el vector de antemano, evitando la necesidad de realocar memoria a medida que el vector crece. Por otro lado,Vec::new
comienza con una capacidad inicial pequeña y luego realoca según sea necesario, lo que puede resultar en más asignaciones de memoria y copias.Sin embargo, en el ejemplo proporcionado, la diferencia de rendimiento entre los dos métodos no es consistente con esta expectativa. Esto puede ser debido al hardware y al sistema operativo específicos que se están utilizando, o a la configuración de optimización del compilador Rust.
Vale la pena señalar que los flamegraphs mostrados en el ejemplo se generan utilizando el perfil de Rust “debug”. Cuando se utiliza el perfil “release” con un nivel de optimización establecido en 3 (
opt-level = 3
), la diferencia de rendimiento entreVec::new
yVec::with_capacity
puede ser menos pronunciada o incluso revertirse.En resumen, aunque en general
Vec::with_capacity
debería ser más rápido queVec::new
, la diferencia de rendimiento puede depender de varios factores y debe ser probada caso por caso, teniendo en cuenta el hardware y la configuración de optimización del compilador.