【问题标题】:Faster map operations in C++C++ 中更快的映射操作
【发布时间】:2021-03-09 20:10:31
【问题描述】:

在我的代码中,我存储了地图中不同组之间交互的参数集。目前在启动时,我添加了每个结构(下面代码中的testvals),其中的键是通过将两个组名连接成一个字符串而创建的。

string nKey = key1;
nKey += JOIN_STRING;
nKey += key2;

map< string, struct> mymap_string; 
mymap_string.insert( make_pair(nKey, testval ));

在查找两个组的数据时,我再次创建该字符串,然后使用地图上的查找来检索我的数据。

string nKey = key1;
nKey += JOIN_STRING;
nKey += key2;

auto it = mymap_string.find( nKey );
if ( it != mymap_string.end() )
{
    struct vals= it->second;
}

在我的代码中,我在启动时创建了一次地图,但查找部分进行了数百万次。我想知道是否有更好的方法来执行此操作,因为字符串连接似乎相对昂贵,并且 find 可能不是搜索和比较字符串的最快方法?

我的测试似乎表明字符串比使用std::pair&lt;string1, string2&gt; 作为地图的键更快。我看过mapunordered_map,但似乎没有太大区别。 unordered_map 可能在键数较多的情况下会稍微快一些。

有没有人对什么可能是更好、更快的方法有任何建议?鉴于对此进行的调用次数,如果我可以显着加快速度,我可以节省大量时间。我不介意插入或设置是否不是非常快,因为它只发生一次,但查找很重要。最好使用适用于 Windows 和 Linux 的标准。

更新:

好的,所以从问题来看,似乎需要更多背景信息。

testvals 是当前正在使用的模型的输入参数的双精度结构,其中提供的变量数量将随模型而变化。但通常这在 4-10 个值之间。此处显示了一个典型的集合:

typedef struct
{
    double m_temp_min;
    double m_temp_max;
    double m_liquid_content;
    double m_growth_rate;
    double m_alpha;
    double m_beta;
} testvals;

Key1 和 Key2 始终是从程序核心模块传递的字符串,但这些字符串是用户定义的,这意味着它们可以是从 "a""my_big_yellow_submarine_3" 的任何字符串。

映射中的键数将取决于数据中的组数。如果只有两组需要提供交互参数,则映射将只有 4 个唯一的字符串键:group1~~group1group1~~group2group2~~group1group2~~group2。通常地图中有 3 或 4 个组类型,因此键的数量通常为数十个。这个大小可能是我看不到mapunordered_map 性能差异很大的原因。

其中一个 cmets 提到了 std::pair&lt;std::string,std::string&gt;,正如我最初所说,调用 make_pair() 的成本似乎远高于制作字符串的成本,并且在我测试时慢了 50% 以上。但我没有尝试std::pairunordered_map 的组合。我假设如果 std::pair 使用 map 速度较慢,那么使用 unordered_map 也会更慢。是否有理由期望它会大不相同?

我希望这有助于澄清一些事情。

【问题讨论】:

  • 请贴出testval的struct type的定义。 struct 有多大?您是在地图中按值存储它还是存储指针/引用? key1key2 究竟采用了哪些类型的值?
  • 我会尝试为键定义一个自定义类型,以及一个严格的弱排序比较器,以便有效地使用两个离散字符串作为键。为了获得额外的荣誉,该键将包含一个包含std::reference_wrapperstd::variant,以便可以构建一个用于查找目的的临时键,而无需复制字符串。
  • unordered_map 将比 map 快,因为内部表示是 O(1) 查找的哈希表(假设有足够的容量和负载因子),而 map 通常是某种形式的搜索-tree,即O( log n ) 用于查找。 unordered_map,在理论上(和实践中)应该总是比 map 更快的查找 - 所以如果 map 更快,那么就会发生一些低效的事情。如何查找您的密钥。
  • @Slava 你读过完整的问题吗?
  • @jpmorr 关于这个你可能会考虑的最后一个想法。当您有字符串的有序映射时,将使用字符串比较操作,它会进行字典排序。因此,为了遍历映射以找到正确的键,它必须遍历字符串,直到字符不匹配。由于您不关心排序,您可以使用自己的比较器首先检查字符串长度,然后检查它们的内容。它可能会有所帮助。无序映射对您来说并不快的原因可能与此有关:它总是需要遍历整个字符串来计算其哈希值。

标签: c++ dictionary associative-array


【解决方案1】:

与实际查找相比,您只有有限数量的键,这使得计算哈希变得昂贵。这就是为什么 std::mapstd::unordered_map 在你的情况下没有太大的不同。除了JOIN_STRING还在计算hash或比较字符串时引入了不必要的操作

我建议您完全避免使用这些组名,而改用组 ID。使用 N 组类型,您只有 N2 种不同类型的交互。那么 ID 将属于 [0, N) 的范围。如果 N 在编译时已知,您甚至可以将其设为数组。所以不是

string nKey = key1;
nKey += JOIN_STRING;
nKey += key2;

你会使用

std::vector<testvals> vals(N*N);    // vector with N² elements

uint32_t nKey = key1*N + key2;      // index of the <key1, key2> mapping
const auto &val = vals[nKey];       // get the mapped value

您应该使用&amp; 来获取参考而不是副本。您也可以使用地图而不是矢量。它仍然比向量慢得多,但仍然比字符串映射快得多。您可以像上面一样计算映射键,或者使用一些映射,如nKey = (key1 &lt;&lt; 16) ^ key2nKey = ((uint64_t)key1 &lt;&lt; 32) | key2

组名仅在您开始将名称转换为 ID 时使用,或者在您想打印出来时使用。您可以使用类似这样的结构来存储名称

struct GroupInfo
{
    std::string groupName;
    uint32_t groupID;
}

无需像在您的代码中那样对 C++ 中的结构使用 typedef。您还可以使用std::vector&lt;std::string&gt;std::map&lt;uint32_t, std::string&gt; 从ID 映射到名称。 ID 可以是较小的类型,例如 uint8_tuint16_t

【讨论】:

  • 好吧,这听起来是个不错的方法——使用 int 作为索引。我有点初学者,所以目前唯一看起来有点令人困惑的是将 ID 分配给 GroupInfo 中的组名,因为我认为没有自动添加新的方法 @987654336 @ 同时检查同名并自动生成新 ID。我还必须每次都查找 GroupInfo 结构来获取 ID,因为我在代码中可以访问的只是字符串 'groupName' - 所以搜索结构不会也很慢吗?
  • @jpmorr 您永远不需要从名称中获取 ID。在您的代码中,您只会在创建地图时使用名称或打印名称以供用户查看。在所有其他地方,仅使用 ID。制作key1key2 整数,而不是将它们存储为字符串,然后获取ID 进行查找
  • 我知道应该只使用 ID,但我每次可以访问的变量是名称。我无法返回并更改主代码以返回 ID 而不是名称,因为该代码已完善并且对我来说是不受限制的。我得到两个字符串来查找我需要的任何信息。因此,使用组合字符串创建地图。在每次查找向量之前,我仍然需要将名称转换为 ID 以生成 int 索引,但这可能不会比地图中的散列函数快。
  • 是的,您确实需要更改整个程序才能使用它。如果你只在一个小函数中传递了 2 个字符串并且不能改变它之外的东西,那么你需要一个不同的方法
  • 但是如果给定了 2 个字符串,然后在函数中多次使用它,那么值得将它们转换为 ID。许多人一直犯的另一个错误是多次查找相同的密钥,例如if (mymap[key] == something) mymap[key] = another_thing。只需将引用或迭代器存储到找到的项目并重用它
猜你喜欢
  • 2016-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-15
  • 2023-03-07
  • 2016-01-31
相关资源
最近更新 更多