【问题标题】:Over Engineering: Using reference (borrow?) on HashMap key in RustOver Engineering:在 Rust 的 HashMap 键上使用引用(借用?)
【发布时间】:2021-01-25 15:42:38
【问题描述】:

我正在制作一种搜索算法 (BFS/DFS) 来搜索游戏状态树。 我已经用 C 语言做了这件事,但我想知道 Rust 是否会更快,因为我一直想学习 Rust,所以我想我会试一试。

我正在尝试将这个东西优化到过度工程的程度(我喜欢它,让我吧),并且我在 C 中做了一个我认为很酷的优化,但我不知道如何去做(/如果可能的话)在 Rust 中。

我的算法大纲是这样的:

Add the start state to a queue, and make a hashmap [State -> Previous State, Action]
// I use this hashmap to find the way back later, and to check if I have been in a state before.

Take a state S0 from the queue:
   for all possible actions, calculate next state S1:
      if S1 is already a key in the hashmap, continue
      and add [ S1 -> S0, action taken ] to the hashmap
      add S1 to the queue,
   ...
   // some other stuff here to check when to stop

我在 C 中所做的优化之一是:我没有将状态结构数据复制到队列中,而是将指向哈希图中相同数据的指针(因为我只是将它添加到哈希图中)复制到队列中,这样我就不必复制状态数据,而队列只包含指向hashmap中相同数据的指针。

这在 C 中很容易实现,因为我有自己的 hashmap 实现,所以很容易创建一个函数来返回指向 hashmap 中正确数据的指针。

在 Rust 中,我现在有了这个:

// Add all possible moves to be searched through later
for m in possible_moves.iter() {
    if let Some(new_state) = popped.do_move(m) { // popped is S0 from my algorithm outline
        if backmap.contains_key(&new_state) { continue; }
        queue.push_back(new_state);
        backmap.insert(new_state, Wayback {
        prev_state: popped, did_move: m });
    }
}

但这意味着new_state数据被复制到队列和hashmap中。 我想知道是否有办法不在 Rust 中复制这些数据,比如我的 C 版本(或其他方式)。

我想要这种优化,因为我只是喜欢它的优化感觉,但在 Rust 的情况下,也是因为现在这个副本意味着我首先拥有 #[derive(Clone, Copy)] 我的所有结构,这在 imo 中不太好,如果有不是一种方法来进行我过度设计的优化,但有一种方法不必派生来复制和克隆我仍然想知道的特征。

非常感谢您的帮助!

附:我知道这不是你的常规代码问题,我的解释可能很不清楚,所以如果对我的意思有任何疑问,我会尽快回答。

【问题讨论】:

  • 如果你直接把这段代码翻译成C,但只在hashmap中存储了一个指向new_state的指针,那么hashmap将在下一次循环迭代时包含一个悬空指针。它可能只是工作,因为你从不取消引用它,但我认为这在技术上仍然是 UB - 至少它在 Rust 中。
  • 实际上是的,如果发生哈希冲突,哈希映射可能会与悬空指针进行相等比较。 UB。
  • 实际上,我将一个指针 to 将 hashmap 中的数据复制到队列中,并且在整个算法完成并且内存完成之前,hashmap 中的任何内容都不会被删除或覆盖丢弃,所以对我来说没有悬空指针,哈希映射包含所有数据,如果我从哈希映射中删除某些内容,队列将有一个悬空指针
  • 1.编译器无法真正知道您是否正在从哈希图中删除数据,这不是其“模型”的一部分,因此它不能乐观地知道或假设您没有删除内容,2。更成问题的是,当您插入一个键时,如果 hashmap 超过了它的负载因子,它可能会移动每个条目的 non-incrementally resize,并且 3. 更成问题的是,取决于 collision resolution algorithm,任何插入都可以移动任何现有条目。
  • 顺便说一句,这不是理论上的,虽然我还没有研究过 hashbrown (/swisstable) 中冲突解决是如何工作的,HashMap 的上一次迭代使用了 robin hood 哈希。

标签: rust hashmap micro-optimization


【解决方案1】:

我想要这种优化,因为我只是喜欢它的优化感觉,但在 Rust 的情况下,也是因为现在这个副本意味着我在所有结构之上都有 #[derive(Clone, Copy)],这不是不错啊

取决于结构的大小……这真的很重要吗? Copy 只是 memcpy

如果没有办法进行过度设计的优化,但有办法不必派生来复制和克隆我仍然想知道的特征。

这里的问题是 Rust 想要每条数据都有一个明确的静态所有者,但这在您的算法中不存在:状态由映射和队列“拥有”。 “共享”不明确所有权的方法是使用Rc,但这会转化为堆分配(引用计数流量在不是原子时并不重要),如果state 可能是一种悲观

【讨论】:

  • 感谢您的回答,我知道 Rust 想要每条数据都有一个明确的所有者,但我的解决方案不是这样吗?我看到它的方式是 hashmap 将拥有数据,而队列将拥有对 hashmap 中数据的引用,或者这不是它在 Rust 中的工作方式? (抱歉,Rust 新手)
  • 技术上是的,问题是 Rust 引用在严格的 R^W 基础上运行,所以你需要在 hashmap 中插入一个键,获取对该键的引用,这将借用 hashmap。围绕哈希图的下一个循环仍然是借用的(它有一个未完成的引用)并且您不能插入其中,因为这需要对哈希图的可变引用,该引用不能与现有的不可变引用共存。它甚至完全有意义:在任何插入(或删除)时,hashmap 都可以调整大小,这会移动每个键和值,从而使未完成的引用无效。
猜你喜欢
  • 1970-01-01
  • 2016-10-26
  • 2022-11-02
  • 2020-09-17
  • 1970-01-01
  • 1970-01-01
  • 2021-04-21
  • 2017-01-30
  • 2022-12-01
相关资源
最近更新 更多