我试图将大HashMap<K, V>
转换为Vec<(K, V)>
。通常的做法是这样的:
// initialize HashMap
let cap = 50000000;
let mut hm: HashMap<usize, usize> = HashMap::new();
for i in 0..cap {
hm.insert(i, i);
}
// convert HashMap to Vec
let vec = hm.into_iter().collect::<Vec<(usize, usize)>>();
如果HashMap
足够大,此代码效果不佳 - 在调用collect()
之初,原始HashMap
仍会在内存中Vec
将分配从Iterator
获得的较小尺寸提示的容量。这会导致内存严重崩溃,因为我真的很大HashMap
s,即使我应该能够在这两种类型之间进行转换而只需要很少的额外内存开销。到目前为止,我已经提出了以下解决方案:
// create small vector
let mut vec: Vec<(usize, usize)> = Vec::with_capacity(100);
for i in hm.into_iter() {
vec.push(i);
// reserve few megabytes
if vec.capacity() - vec.len() < 10 {
vec.reserve_exact(1000000);
}
}
这个问题有更好的(更高效或更惯用)方法吗?如果要提高性能,我愿意使用unsafe
代码。
修改
正如所指出的那样into_iter
在迭代期间不会解除分配,因此所提出的解决方案不能按预期工作。除了将HashMap
转储到文件然后将该文件读入Vec
之外,还有其他方法可以转换这些集合吗?
答案 0 :(得分:4)
预先分配所需的确切金额是内存和时间效率的解决方案。
假设您要创建包含100个项目的向量。如果要为50个项目分配空间,当您添加项目51时,存在两种可能性:
不可能知道哪种情况会发生,所以你必须承担最坏的情况。
这是Iterator
使用size_hint
方法的原因之一:知道要分配多少项更有效。
另一方面,HashMap
可能会将数据存储在一个大的分配中,因为它更有效。这意味着将一个项目移出然后减少分配是不可能的(或者可能不容易/有效)。即使你可以这样做,在副本开头你也可以分配整个HashMap
和Vec
。
我可以想到两种可能改善情况的可能性:
HashMap
将数据内部存储在Vec
中,则可能会向HashMap
添加一个方法,该方法会在最后一分钟的清理后返回Vec
。< / LI>
HashMap
和/或Vec
。例如,如果您需要迭代数据,则首先不需要collect
到Vec
;只是迭代它。答案 1 :(得分:1)
您似乎对Vec
FromIterator
特征的实施感到不满意。我不知道在std中更改它是否合理。但是,您可以为Vec
引入一个包装器,并根据需要实现FromIterator
:
#[derive(Debug)]
struct OptimizedVec<T>(Vec<T>);
impl<T> std::iter::FromIterator<T> for OptimizedVec<T> {
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> OptimizedVec<T> {
let mut vec = Vec::with_capacity(100);
for i in iter {
vec.push(i);
// reserve few megabytes
if vec.capacity() - vec.len() < 10 {
vec.reserve_exact(1000000);
}
}
OptimizedVec(vec)
}
}
//...
let vec: OptimizedVec<_> = hm.into_iter().collect();
Vec
值可以vec.0
访问。