【问题标题】:std::map (and family) lookup performance issuesstd::map(和家庭)查找性能问题
【发布时间】:2016-09-29 08:44:59
【问题描述】:

我正在编写一个位域抽象类,它围绕一块 32 位内存 (u32 = unsigned int) 并提供对该内存中各个位或范围的访问。

为了实现这一点,我使用了一个 std::map,其中唯一键是 pointer(不是 std::string),指向表示助记符的 C 字符数组,其值为包含位域属性(如助记符、起始位置、长度、初始值和字段值)的结构。所有这些属性都是常量并在启动时定义,除了仅在基础 u32 值更改时更改的字段值。 (另请注意:我刚刚重用了助记指针值作为唯一键)。

这是在模拟器中使用的,其中 getBitfieldValue() 每秒被调用多次,它返回位域值(只读)。

在 VS 2015 更新 3 下编译和分析代码(使用 -O2 和我能找到的任何速度优化),它表明 getBitfieldValue() 函数和扩展 std::find() 占用了大约 60-70%总 CPU 时间...太慢了。

我尝试过使用其他地图实现,例如 Boost::flat_mapgoogle::dense_hash_mapstd::unordered_map,它们有些帮助,但最终还是太慢了 (~50-60%)。

我的猜测是我将映射用于错误的目的,但考虑到只有 5-20 个位域映射(查找大小很小),我不确定......这似乎太慢了。大部分时间也会花在查找相同的字段上。

相关类源码可以在这里找到:BitfieldMap32

地图在启动时如何初始化的示例(仅运行一次):

struct Fields
{
    static constexpr char * ADDR = "ADDR";
    static constexpr char * SPR = "SPR";
};
ExampleClass() // constructor
{
    // registerField(mnemonic, start position, length, initial value)
    registerField(Fields::ADDR, 0, 31, 0);
    registerField(Fields::SPR, 31, 1, 0);
}

以及如何访问字段值(只读):

// getFieldValue definition.
const u32 & BitfieldMap32_t::getFieldValue(const char* fieldName)
{
    return mFieldMap.find(fieldName)->second.mFieldValue;
}

// Field access.
const u32 value = ExampleClassPointer->getFieldValue(Fields::ADDR)

关于如何减少查找时间的任何想法?还是我需要一起更改实现?

【问题讨论】:

  • 为什么不使用 std::bitset ?
  • 因此您测量到您的代码使用了如此多的 CPU。但是,真的是吗? CPU 或运行时间的百分比是否真的“足够快”并不重要。你的程序的运行时间是多少?它必须等待用户输入吗?如果用户输入一些东西,用户是否必须等待超过几毫秒?用户需要等待多长时间?任何不到半秒的时间实际上看起来几乎是瞬间的。你真的想让事情复杂化(可能是很多)以在 0.4 秒而不是 0.5 秒内运行吗?
  • 也许你应该考虑使用std::unordered_map,它使用散列并且应该更快,而std::map使用二进制搜索。
  • 如果您真的有 5-20 个位域映射,那么您可能会发现向量上的线性迭代比不同类型的映射工作得更快。
  • 1.不确定使用 std::bitset 是什么意思-至少我必须在范围内执行迭代,因为它没有内置“范围”运算符。 2. 是的,它在现实世界中的性能很慢。模拟 CPU 运行在 ~300 MHz,我非常怀疑它目前是否接近该值。最终将应用用户输入。 3. 已经尝试过 std::unordered_map,没有显着帮助。 4. 如果没有其他帮助,我也可以按照其中一个答案的建议尝试。

标签: c++ c++11 dictionary stdmap bit-fields


【解决方案1】:

IIUC,使用字典(std::mapstd::unordered_map)是一个巨大的矫枉过正。也许您应该使用以下内容:

  1. 该类应该只是一个整数内部存储的包装器(或最多一个std::bitset)。

  2. 助记符应该是enums,而不是std::strings。

  3. 在内部,有一个std::vector 有效地将每个enum 值映射到bitmask。 (如果您使用的是 c++11 enums,请参阅 here 如何将 enum 值转换为 std::vector 内的位置)。

  4. 每个操作都应该只取助记符,通过索引找到位掩码,并将其应用于内部存储。

【讨论】:

  • 正如我的评论中提到的,当我需要迭代 bitset 时,我不确定 std::bitset 将如何提高性能,因为它不提供“范围”功能(并不总是单个位)。我将尝试枚举和向量的组合,看看效果如何。
  • @marco9999 那我再看看你的代码。无论如何,我建议您考虑将std::string 助记符替换为enums。
  • 最终使用了 std::vector 和从 0 开始的有序索引键(枚举/整数)。效果更好。猜猜地图是矫枉过正。
  • @marco9999 很高兴它对您有所帮助。抱歉,我没有机会进一步研究您的代码(工作,您知道...)。一切顺利。
猜你喜欢
  • 1970-01-01
  • 2011-01-19
  • 2010-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多