【问题标题】:Efficient node extract() + insert() in `unordered_map``unordered_map`中的高效节点提取()+插入()
【发布时间】:2021-10-13 18:36:13
【问题描述】:

使用 C++17 node-extract() 函数,我可以更改密钥,而无需重新分配节点。在我的特定用例中,我将键替换为相等的键,因此我想使用 insert()-with-hint 来避免完整的第二次查找,并且在使用 std::map:

struct Replacement { std::string from, to; };
std::map<std::string_view, Replacement> replacements;
~~~~
std::string from = ~~~;
std::string to = ~~~:
auto [it, inserted] = replacements.try_emplace(from, std::move(from), std::move(to));
if (inserted) {
    // need to patch up the key, which points to invalid data now:
    auto node = replacements.extract(it++);    // 1
    node.key() = node.mapped().from;           // 2
    replacements.insert(it, std::move(node));  // 3
}

当我insert() 是一个新节点时,它的key_type 将继续指向from.data(),但是,作为mapped_type 构造的一部分,它会被移出。由于 SSO(小字符串优化),移动字符串的 data() 可能与源字符串的 data() 不同,一旦 from 的生命周期结束,key_type 就会失效,可能更早。所以我们需要重新设置key_type指向mapped_type,为此我们(1)提取节点,确保it在extract()之前递增保持有效,然后(2)根据需要修补密钥,最后 (3) 在旧位置重新插入节点,由提示 it 给出。

到目前为止一切顺利。现在用unordered_map试试同样的方法:

~~~~
std::string from = ~~~;
std::string to = ~~~:
auto [it, inserted] = replacements.try_emplace(from, std::move(from), std::move(to));
if (inserted) {
    // need to patch up the key, which points to invalid data now:
    auto node = replacements.extract(it++);    // only thing possible, but wrong direction
    // auto node = replacements.extract(it--); // right direction, but ERROR: unordered_map isn't bidirectional
    node.key() = node.mapped().from;           // 2
    replacements.insert(it, std::move(node));  // 3
}

在这里,我遇到了问题,unordered_map,作为vector&lt;forward_list&gt;,只有前向迭代器,所以我不能,因为我不得不,向后记住最终插入的确切插入点-hint (3) 按迭代顺序。看来我只有两个选择:

  1. 在没有提示的情况下使用 insert(node),导致完整的第二次查找
  2. 增加迭代器并将其作为提示传递。 std 表示插入是尽可能接近提示执行的,但是如果我的哈希表与良好性能所需的一样稀疏,则至少使用桶内迭代器是仅向前的,则递增的迭代器可能在桶列表中排几个桶,使提示无用。

所以,好吧,这个提示对unordered_map 没用。为了保存问题,我们来谈谈unordered_multimap,然后:)

在unordered_map 中,提示可能很有用,因为与unordered_map 不同,unordered_multimap 需要扫描存储桶以将新节点放入密钥的潜在equal_range() .有没有比后增量或根本不产生提示更好的方法?

【问题讨论】:

  • 我只想说:如果扫描存储桶是一个性能问题,那么您应该考虑使用常规的map,但是谁不喜欢一个好的“Gedankenexperiment”呢?
  • 是否将from 和std::move(from) 都传递给同一个函数调用定义的行为?
  • 使用地图,完整查找需要 O(log n) 时间,提供插入提示可以将其减少到 O(1)。使用无序地图,即使进行完整查找,您也有 O(1) 时间。将提示插入未绘制的地图是否使其在实践中更有效是值得怀疑的。一些基准测试可能有用。
  • 顺便说一句,使用地图,提示是“迭代器到将插入新元素的位置”。你不应该增加it 而不是减少吗?
  • @Enlico 取决于实现细节不会使代码行为未定义。遇到未定义的行为是未定义的行为。在这种情况下,如果两个实现细节都对我们不利,最坏的情况是存储在节点中的哈希是根据从 std::string 移出的数据计算得出的,这是臭名昭著的有效-但未指定的状态。因此,我们得到了一个节点,该节点的关键引用从数据中移出,并且哈希值是错误的。两者都通过在extract()ing 之后和insert()ing 之前分配一个新键来修复,重新建立不变量。

标签: c++ c++17 string-view unordered-multimap insertion-with-hint


【解决方案1】:

如果您的唯一目标是保留值的密钥调整,则无需提取任何内容:只需将密钥包装在一个将其作为 mutable 成员(或通过 std::unique_ptr 保存)的类中,以便您可以在地图中合法地更改它。您必须为包装器类型定义比较/散列操作,但这只不过是迭代器诡计的代码。

【讨论】:

  • 好主意,谢谢!对于mutable,它甚至应该被定义为行为。
猜你喜欢
  • 1970-01-01
  • 2013-03-11
  • 1970-01-01
  • 1970-01-01
  • 2020-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-08
相关资源
最近更新 更多