【问题标题】:Memory efficient conversion between a HashMap and a VecHashMap 和 Vec 之间的内存高效转换
【发布时间】:2016-08-22 09:34:05
【问题描述】:

我正在尝试将大的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。这会导致非常大的HashMaps 出现内存不足的恐慌,即使我应该能够在这两种类型之间进行转换,而且只需要很少的额外内存开销。到目前为止,我想出了以下解决方案:

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

【问题讨论】:

  • 你确定你的第二个代码有更少的内存开销吗?我认为IntoIter 迭代器不会在迭代期间释放内存。实际上,用很少的额外内存来进行这个对话并不容易......
  • 如果没有足够的内存来同时存储HashMapVec,您可能需要切换计算机,或者重组您的程序以便能够处理更小的块工作量(例如 MapReduce)。事实上,您的空间非常小:如果问题规模增加 50%,您很可能会因为 HashMap 而出现 OOM,然后您将怎么做?跨度>

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


【解决方案1】:

预先分配所需的确切数量节省内存和时间的解决方案。

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

  1. 分配可以原地延长,您可以继续愉快地前进。
  2. 分配无法就地扩展,因此进行了新的、更大的分配。所有的数据都需要从之前的分配中复制过来;可能是 O(n) 操作。在此复制期间,两个分配都处于活动状态,占用 50 + 100 个插槽,比原始分配正确调整大小时更多空间。

不可能知道会发生哪种情况,所以你必须假设最坏的情况。

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

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

我认为有两种可能可以改善这种情况:

  1. 如果HashMap 将数据内部存储在Vec 中,则可能会向HashMap 添加一个方法,该方法在最后一分钟清理后返回Vec
  2. 完全避免存储HashMap 和/或Vec。例如,如果您需要对数据进行迭代,则无需先将collect 转换为Vec;只需对其进行迭代。

【讨论】:

  • 我想我记得 HashMap 使用 3 个向量,编码为:(哈希、键、值)。因此,没有从HashMap&lt;usize, usize&gt;Vec&lt;(usize, usize)&gt; 的简单转换。
【解决方案2】:

您似乎对VecFromIterator trait 的实现不满意。我不知道在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 访问。

【讨论】:

  • 除非我完全误解了某些东西,否则在std 中修复它绝对是不合理的。内存优化实现将比现在完成的方式慢得多。我也怀疑 OP 自己的实现是否有帮助...
  • 我打算将该代码包装在自定义结构中,但为了问题的简单性,我没有发布它。我知道 stds 的实现不应该改变,因为这会产生巨大的时间影响。我的用例很少见,我想知道是否有比连续的reserve_exact 调用更好的方法。
  • 这个想法是您保留相对较小的项目块,这样推送就不必重新分配。我将编辑问题以避免错误信息(我认为 into_iter 在通过迭代器时会释放内存)。
猜你喜欢
  • 2016-12-14
  • 1970-01-01
  • 2020-10-02
  • 1970-01-01
  • 2012-08-22
  • 1970-01-01
  • 2020-06-06
  • 1970-01-01
相关资源
最近更新 更多