Performance HashMap和Vec之间的高效内存转换

Performance HashMap和Vec之间的高效内存转换,performance,memory-management,collections,rust,out-of-memory,Performance,Memory Management,Collections,Rust,Out Of Memory,我正在尝试将一个大的HashMap转换为Vec。通常的做法如下所示: // 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<(us

我正在尝试将一个大的
HashMap
转换为
Vec
。通常的做法如下所示:

// 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)>>();
有没有更好(更有效或更惯用)的方法来解决这个问题?如果要提高性能,我愿意使用
不安全的
代码

编辑
正如所指出的,
到iter
在迭代过程中不会解除分配,因此建议的解决方案不能按预期工作。除了将
HashMap
转储到文件中,然后将该文件读入
Vec
之外,还有其他方法可以转换这些集合吗?

您似乎对
Vec
fromtiterator
特性的实现不满意。我不知道在std中更改它是否合理。但是,您可以为
Vec
引入一个包装器,并根据自己的意愿实现
fromtimiterator

#[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();
#[派生(调试)]
结构优化DVEC(Vec);
impl std::iter::用于OptimizedVec的FromIterator{
#[内联]
fn来自国际热核实验堆(iter:I)->优化DVEC{
设mut-vec=vec::具有_容量(100);
国际热核聚变实验堆{
向量推(i);
//保留几兆字节
如果向量容量(){
向量储备(1000000);
}
}
优化DVEC(vec)
}
}
//...
让vec:OptimizedVec=hm.into_iter().collect();

Vec
值将作为
Vec.0

访问,预先分配所需的准确数量是节省内存和时间的解决方案

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

  • 分配可以适当延长,你可以继续快乐的生活
  • 分配无法在适当的位置进行扩展,因此需要进行新的、更大的分配。所有数据需要从上一次分配中复制;可能是O(n)操作。在此复制期间,两个分配都处于活动状态,占用50+100个插槽,比原始分配的大小适当时占用更多空间
  • 不可能知道哪种情况会发生,所以你必须假设最坏的情况

    这是
    Iterator
    具有
    size\u hint
    方法的原因之一:知道要分配多少项更有效

    另一方面,
    HashMap
    可能将数据存储在一个大的分配中,因为它更高效。这意味着不可能(或者可能不容易/有效)将一个项目移出,然后减少分配。即使您可以这样做,在拷贝开始时,您将同时分配整个
    HashMap
    Vec

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

  • 如果
    HashMap
    将数据内部存储在
    Vec
    中,则可能会在
    HashMap
    中添加一个方法,该方法在最后一分钟清理后返回
    Vec
  • 完全避免存储
    HashMap
    和/或
    Vec
    。例如,如果您需要对数据进行迭代,则不需要先
    收集
    Vec
    ;只需重复它

  • 您确定第二个代码的内存开销更小吗?我认为,
    IntoIter
    迭代器不会在迭代过程中释放内存。实际上,用很少的额外内存进行对话并不容易……如果没有足够的内存同时存储
    HashMap
    Vec
    ,您可能需要切换计算机,或者重新构造程序,以便能够处理较小的工作块(例如MapReduce)。实际上,您的净空很小:如果问题大小增加了50%,您很可能只需要
    HashMap
    ,然后您将怎么办?除非我完全误解了某些内容,否则在
    std
    中修复它肯定是不合理的。内存优化实现将比现在的实现方式慢得多。我也怀疑OPs自己的实现是否有帮助……我曾计划将代码包装到自定义结构中,但为了问题的简单性,我没有发布它。我理解不应该更改
    std
    s的实现,因为这会带来巨大的时间影响。我的用例非常罕见,我想知道是否有比连续的
    reserve\u-exact
    调用更好的方法。这个想法是你保留一个相对较小的项目块,这样push就不必重新分配。我将编辑这个问题以避免错误信息(我认为into_iter在遍历迭代器时会释放内存)。我想我记得
    HashMap
    使用了3个向量,编码如下:(哈希、键、值)。因此,没有从
    HashMap
    Vec
    的简单转换。
    #[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();