HashMap和Vec

时间:2016-08-22 09:34:05

标签: performance memory-management collections rust out-of-memory

我试图将大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之外,还有其他方法可以转换这些集合吗?

2 个答案:

答案 0 :(得分:4)

预先分配所需的确切金额内存和时间效率的解决方案。

假设您要创建包含100个项目的向量。如果要为50个项目分配空间,当您添加项目51时,存在两种可能性:

  1. 可以在适当的时候扩展分配,继续你的快乐方式。
  2. 无法对分配进行扩展,因此需要进行新的更大分配。所有数据都需要从之前的分配中复制出来;可能是O(n)操作。在此副本期间,两个分配都是实时的,占用50 + 100个插槽,更多空间,而不是原始分配的大小合适。
  3. 不可能知道哪种情况会发生,所以你必须承担最坏的情况。

    这是Iterator使用size_hint方法的原因之一:知道要分配多少项更有效。

    另一方面,HashMap可能会将数据存储在一个大的分配中,因为它更有效。这意味着将一个项目移出然后减少分配是不可能的(或者可能不容易/有效)。即使你可以这样做,在副本开头你也可以分配整个HashMapVec

    我可以想到两种可能改善情况的可能性:

    1. 如果HashMap将数据内部存储在Vec中,则可能会向HashMap添加一个方法,该方法会在最后一分钟的清理后返回Vec。< / LI>
    2. 完全避免存储HashMap和/或Vec。例如,如果您需要迭代数据,则首先不需要collectVec;只是迭代它。

答案 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访问。