【问题标题】:Is there better option than map?有比地图更好的选择吗?
【发布时间】:2017-08-28 08:14:55
【问题描述】:

好吧,我正在制作一个 c++ 程序,它会处理很长的符号流,我需要存储信息以供进一步分析,流中出现特定长度的符号序列。例如在二进制流中

100110010101

我有一个长度为 6 的序列,例如:

  • 100110 从位置 0 开始
  • 001100 从位置 1 开始
  • 011001 从位置 2 开始

我需要存储的是可以找到一个特定序列的所有位置的向量。所以结果应该是一个表格,可能类似于一个哈希表,如下所示:

顺序/位置

10010101 | 1 13 147 515

01011011 | 67 212 314 571

00101010 | 2 32 148 322 384 419 455

等等。

现在,我发现将字符串映射到整数很慢,所以因为我在流中预先掌握了有关符号的信息,所以我可以使用它将这个固定长度的序列映射到整数。

下一步是创建一个映射,将这些“代表整数”映射到表中的相应索引,我在其中添加该序列的下一次出现。然而,这很慢,比我能承受的要慢得多。我尝试了 std 和 boost 库的有序和无序映射,没有一个有足够的效率。我测试了一下,地图才是这里真正的瓶颈

这是伪代码中的循环:

for (int i=seqleng-1;i<stream.size();i++) {
    //compute characteristic value for the sequence by adding one symbol
    charval*=symb_count;
    charval+=sdata[j][i]-'0';
    //sampspacesize is number off all possible sequence with this symbol count and this length
    charval%=sampspacesize;
    map<uint64,uint64>::iterator &it=map.find(charval);
    //if index exists, add starting position of the sequence to the table
    if (it!=map.end()) {
        (table[it->second].add(i-seqleng+1);
    }
    //if current sequence is found for the first time, extend the table and add the index
    else {
        table.add_row();
        map[charval]=table.last_index;
        table[table.last_index].add(i-seqleng+1)
    }
}

所以问题是,我可以使用比地图更好的东西来记录表中相应的索引,还是这是最好的方法?

注意:我知道这里有一种快速的方法,那就是为每个可能的符号序列创建足够大的存储空间(这意味着如果我有长度为 10 和 4 个符号的序列,我会保留 4^10 个插槽并且可以省略映射),但我将需要处理符号的长度和数量,这会导致保留的内存量超出计算机的容量。但是实际使用的slot数不会超过1亿(这是由最大流长度保证的),并且可以存储在计算机中。

如果有什么不清楚的地方,请提出任何问题,这是我在这里的第一个大问题,所以我缺乏经验来表达自己的其他人会理解的方式。

【问题讨论】:

  • 您要将序列映射到位置,还是将位置映射到序列?
  • @RichardHodges 数字分为三种,一种是表示序列的数字,一种是表中的索引,一种是位置。我想将代表序列的数字映射到表中的索引(在这个索引下是这个序列的位置)。
  • 看来你有std::map&lt;sequence, tableIndex&gt; mapMyVector&lt;MyVector&lt;Position&gt;&gt;。为什么不直接std::map&lt;sequence, std::vector&lt;Position&gt;&gt; map
  • @Jarod42 它更慢了。地图中的结构越复杂,加载循环越慢。我一路下降到一次只加载一个符号并将整数映射到整数,但是从这里我无法提高速度,因为这些是最简单的数据类型,除了使用我的 map 之外别无他法知道。
  • 如果您只使用符号10,那么为什么不将其存储为unordered_map,例如uint_8t,其中您的密钥是数字 对应你的二进制展开?读取源字符串时,您只需要逐个字符地读取,边走边写。

标签: c++ optimization micro-optimization


【解决方案1】:

具有预分配空间的无序映射通常是存储任何类型的稀疏数据的最快方式。

鉴于 std::string 有 SSO,我不明白为什么这样的事情不会像它得到的一样快:

(我使用了 unordered_multimap 但我可能误解了要求)

#include <unordered_map>
#include <string>
#include <iostream>

using sequence = std::string; /// @todo - perhaps replace with something faster if necessary

using sequence_position_map = std::unordered_multimap<sequence, std::size_t>;


int main()
{
    auto constexpr sequence_size = std::size_t(6);
    sequence_position_map sequences;
    std::string input = "11000111010110100011110110111000001111010101010101111010";

    if (sequence_size <= input.size()) {
        sequences.reserve(input.size() - sequence_size);

        auto first = std::size_t(0);
        auto last = input.size();

        while (first + sequence_size < last) {
            sequences.emplace(input.substr(first, sequence_size), first);
            ++first;
        }
    }

    std::cout << "results:\n";
    auto first = sequences.begin();
    auto last = sequences.end();
    while(first != last) {
        auto range = sequences.equal_range(first->first);

        std::cout << "sequence: " << first->first;
        std::cout << " at positions: ";
        const char* sep = "";
        while (first != range.second) {
            std::cout << sep << first->second;
            sep = ", ";
            ++first;
        }
        std::cout << "\n";
    }
}

输出:

results:
sequence: 010101 at positions: 38, 40, 42, 44
sequence: 000011 at positions: 30
sequence: 000001 at positions: 29
sequence: 110000 at positions: 27
sequence: 011100 at positions: 25
sequence: 101110 at positions: 24
sequence: 010111 at positions: 46
sequence: 110111 at positions: 23
sequence: 011011 at positions: 22
sequence: 111011 at positions: 19
sequence: 111000 at positions: 26
sequence: 111101 at positions: 18, 34, 49
sequence: 011110 at positions: 17, 33, 48
sequence: 001111 at positions: 16, 32
sequence: 110110 at positions: 20
sequence: 101010 at positions: 37, 39, 41, 43
sequence: 010001 at positions: 13
sequence: 101000 at positions: 12
sequence: 101111 at positions: 47
sequence: 110100 at positions: 11
sequence: 011010 at positions: 10
sequence: 101101 at positions: 9, 21
sequence: 010110 at positions: 8
sequence: 101011 at positions: 7, 45
sequence: 111010 at positions: 5, 35
sequence: 011101 at positions: 4
sequence: 001110 at positions: 3
sequence: 100000 at positions: 28
sequence: 000111 at positions: 2, 15, 31
sequence: 100011 at positions: 1, 14
sequence: 110001 at positions: 0
sequence: 110101 at positions: 6, 36

【讨论】:

  • 感谢您的提示,我会尝试一下,看看我是否可以提高一些速度。
  • 对不起,我弄错了,不是。速度差不多,我忘了用multimap,只用map。
  • @TStancek 所以我们可以尝试将字符串转换为位 - 这会稍微加快比较速度。
  • 我已经这样做了,不是位,而是整数。问题是我认为在有序映射中,在这种情况下它比无序要慢得多,它始终保持平衡。我可以强制它创建一个具有固定深度的平衡树,这样节点中的每个存储元素都不会移动吗?
  • @TStancek 不久前我做了一些测试来比较无序和有序地图的性能。结果是可以预测的,并且非常有利于无序地图。 stackoverflow.com/questions/36392583/…
【解决方案2】:

在 cmets 和 answer 中提出许多建议后,我测试了其中的大多数并选择了最快的可能性,将映射造成的瓶颈减少到几乎与没有“地图”的情况下运行的时间相同(但产生了不正确的数据,但是我需要找到可以降低到的最低速度)

这是通过将unordered_map&lt;uint64,uint&gt;vector&lt;vector&lt;uint&gt;&gt; 替换为unordered_map&lt;uint64, vector&lt;uint&gt; &gt; 来实现的,更准确地说是boost::unordered_map。我还用unord_map&lt;string,vector&lt;uint&gt;&gt; 对其进行了测试,令我惊讶的是它并没有我预期的那么慢。不过速度比较慢。

另外,可能是由于ordered_map 移动节点以在其内部结构中保持平衡树,ord_map&lt;uint64, vector&lt;uint&gt;&gt;ord_map&lt;uint64,uint&gt;vector&lt;vector&lt;uint&gt;&gt; 慢一点。但是由于unord_map 在计算过程中不会移动其内部数据,因此它似乎是可以使用的最快的配置。

【讨论】:

    猜你喜欢
    • 2012-09-11
    • 1970-01-01
    • 2011-08-07
    • 1970-01-01
    • 2021-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多