假设我们将数百万个项目(从DB表,JSON文件等)读入Vec<Item>
:
#[derive(Clone)]
struct Item {
id: u64,
name: String,
//...
}
let items: Vec<Item> = load_items();
通常,为了有效地处理这些数据,我们需要额外的数据结构,比如一个HashMap,其中一个item的字段作为键,item本身作为值。在我看来,最简单的方法是这样的:
let map_by_name: HashMap<String, Item> = items.iter().map(|x| (x.name.clone(), x.clone())).collect();
这很简单,但这里我们克隆所有数据,这显然在性能和内存使用方面不是最佳的。
在基于GC的语言(Java,.NET,Python,JS等)中,这个特殊的问题不存在-最简单和最自然的实现不会涉及克隆,而只是存储对vector中对象的额外引用。
在Rust,AFAIK中,我们不能移动或引用向量元素。那么最有效的替代方案是什么呢?以下想法浮现在脑海中:
1.使用Rc
:
let items: Vec<Rc<Item>> = load_items();
let map_by_name: HashMap<String, Rc<Item>> = items.iter().map(|x| (x.name.clone(), Rc::clone(&x))).collect();
1.在HashMap值中,我们只将元素的索引存储在vector中:
let map_by_name: HashMap<String, usize> = items.iter().enumerate().map(|(i, x)| (x.name.clone(), i)).collect();
我的理解是正确的吗?第二种方法通常比第一种方法更有效,但是如果我们需要将map传递给其他函数,它可能不太方便,因为它总是需要我们传递向量。
还有第三种方法是我所缺少的吗?
P.S.:在上面的例子中,我使用了一个克隆的String
作为密钥。很明显,如果对特定情况有意义,我们也可以使用通过x.name.as_ref()
获得的&str
,但我想特别关注更大的问题-项目本身。
4条答案
按热度按时间yduiuuwa1#
存储索引-您的第二个示例-不一定更有效/更快,因为
Rc<T>
只是指向RcInner<T>
的指针,该指针保存引用计数器和T
。实际上,Rc<T>
与usize
-index具有相同的大小。查找时间基本相同,因为索引到Vec
中并从Rc<T>
解引用T
基本上是存储器提取。然而,当创建查找Map时,由于Rc<Item>
的引用计数器需要增加,所以Rc<T>
存在更多开销,而索引方法仅假设项目将在它们最初被发现的地方。最后一点是什么会告诉我选择哪种方法:如果
Vec<Item>
和HashMap<Key, usize>
可以紧密耦合-例如,在同一个结构体或同一个函数中,它们不能转义-那么使用索引可能更方便。但是,如果查找Map到处浮动,那么当
Vec
发生变化时,Map中保存的索引总是有不正常的危险(例如,稍后有人可能会在索引已经被收集之后放入对Vec
进行排序的代码,从而破坏耦合)。我会使用Rc<Item>
来 * 保证 * 查找Map只包含有效的引用。pdkcd3nj2#
我写了一个简单的测试,比较了以下内容:
Vec<Item>
、HashMap<String, Item>
(带克隆)。Vec<Item>
、HashMap<&str, &Item>
Vec<Rc<Item>>
、HashMap<&str, Rc<Item>>
Vec<Item>
、HashMap<&str, usize>
我在Ubuntu 20.04机器上测试了几次,在发布模式下进行了优化。结果显示变体2和4是最快的。变体3较慢,当然1是最慢的。注意,在这个测试中,我只比较了 * 创建 * 向量和HashMap的时间。我没有测试查找时间。
下面是测试代码:
结果是:
除了单个迭代中的时间之外,我们还应该注意每个测试类型的“总”运行时间。它包括
Rc
,特别是Clone
变体的单独测试运行之间的显著更多的额外运行时间。我认为这背后的原因是,对于这些变体,有更多的数据需要 * 删除 *在每次test_*
函数调用结束时。ecr0jaav3#
对我来说,这些问题主要集中在内存上,所以我使用size_of crate,criterion和User的at54321代码进行了内存基准测试:
结果如下:
编辑:根据情况,即使是BOX也可能有所帮助:根据应用程序和使用情况,即使是Box也可能有所帮助:
wn9m85ua4#
你提到(强调我的):
在Rust,AFAIK中,我们不能移动或引用向量元素。
我们实际上可以将元素移出vector!假设你不需要保留vector,我们可以使用drain()方法将所有元素移出vector并移入一个hashmap,如下所示: