"Flattening" std::set<std::string> 用于存储和比较?

"Flattening" std::set<std::string> for storage and comparison?

本文关键字:std 存储 用于 比较 gt lt Flattening string set      更新时间:2023-10-16

这可能是一个愚蠢的问题,基于std :: set&lt;>已经具有完美的比较操作员,但是我认为我可能对我的特定用例有优化为了确保我不会以某种方式伤害自己。

本质上,我有一个昂贵的操作,该操作将作为输入的std :: set&amp;。我正在缓存该操作的结果,因此,如果已经传递了相同的输入,我只能返回结果。这确实需要存储集合的副本(我在A

中进行的副本
std::map<std::set<std::string>, Result*>

,然后每次调用操作时进行搜索。由于很有可能会连续将同一操作称为数千次,因此我要说的是,cached std :: Set> 99%的时间> 99%。我最近尝试了我认为可能是一个很小的改进,因为某些字符在传递字符串中是无效的事实:我将std ::设置为单个字符串,将组件字符串划定为'::' 特点。我的std ::地图变成

std::map<std::string, Result*> 

每次调用操作时,集合都会扁平,并且在缓存中搜索了单个字符串。

我实际上对性能提高感到惊讶。我的测试运行使用了std ::集,其中包含5个字符串,每个30个字符的长度以及10,000,000个搜索。在我的工作站上,每次运行的时间都是

 std::map<std::set<std::string>, Result*> : 138.8 seconds
 std::map<std::string, Result>            : 89.2  seconds

看来,即使在每次呼叫的设置上的开销中,第二种方法也是一个巨大的进步。我想我的问题是:为什么?我在这里做的可能不好的事情是有目的地避免了STD ::的实施者(即可能导致较大的字符串造成不良的堆碎片?)是仅仅是因为集合中的单个字符串在不同的位置都在不同的位置进行比较,并且必须分开比较?我在脚上开枪吗?在这种特定情况下,似乎太明显了,可以提高这种性能。

为什么?

数据位置。

std::set通常以二进制搜索树的形式实现。与std::set相比,由于使用std::string的计算机,搜索操作可能更快。

我将考虑在围绕其地址和版本号的围绕集合编写一个小包装。它将包括修改集合的操作(插入,擦除等)的过载,当发生插入/擦除时,它会增加版本号。

为了确定平等,您只查看两件事:集合的地址和版本号。如果修改相当罕见,并且对平等的测试相当普遍,那么比较节省的时间可能要比跟踪变化所花费的时间大得多 - iow,您将获得很大的速度胜利。

如果您必须编写完整包装器(揭露set的功能的全部的包装器),这可能是很多工作。在大多数情况下,这是不必要的;大多数典型的代码将仅需要几个函数可见 - 通常只有两个或三个。

#include <iostream>
#include <set>
#include <utility>
template <class T>
class tracked_set {
    std::set<T> data;
    size_t version = 0;
public:
    typedef typename std::set<T>::iterator iterator;
    std::pair<iterator, bool> insert(T &&d) {
        auto ret = data.insert(std::forward<T>(d));
        version += ret.second;
        return ret;
    }
     iterator erase(iterator i) {
         auto ret = data.erase(i);
         if (ret != data.end())
             ++version;
     }
    // At least if memory serves, even non-const iterators on a `set` don't 
    // allow the set to be modified, so these should be safe.
    auto begin() { return data.begin(); }
    auto end() { return data.end(); }
    auto rbegin() { return data.rbegin(); }
    auto rend() { return data.rend(); }
    // The `c*` iterator functions return const_iterator's, so 
    // they're definitely safe.
    auto cbegin() const { return data.cbegin(); }
    auto cend() const { return data.cend(); }
    auto crbegin() const { return data.crbegin(); }
    auto crend() const { return data.crend(); }
    class token {
        std::set<T> const *addr;
        size_t version;
    public:
        friend bool operator==(token const &a, token const &b) {
            return a.addr == b.addr && a.version == b.version;
        }
        token(tracked_set const &ts) { 
            addr = &ts.data;
            version = ts.version;
        }
    };
    operator token() const { return token(*this); }
};
int main() {
    using T = tracked_set<int>;
    T ts;
    ts.insert(1);
    ts.insert(2);
    T::token t(ts);
    if (t == T::token(ts))
        std::cout << "Goodn";
    ts.insert(3);
    if (t == T::token(ts))
        std::cout << "badn";
}