在c ++性能问题中使用getter返回映射

时间:2018-02-20 22:05:31

标签: c++ std stdmap rvo

我有一个类,它有3-4个类型std::map<string, vector<string>>的数据成员,用于缓存数据。它的实例创建一次,数据填入服务调用的所有映射中。我有用于访问这些地图的getter函数。 (这里也有一些线程锁定逻辑)

这些getter函数被调用了很多次,我很担心性能,因为地图对象被复制很多次。

class A {
  private:
    map<string, vector<string>> m1;
    map<string, vector<string>> m2;
    map<string, vector<string>> m3;

  public:
    map<string, vector<string>> getm1() { return m1; }
    map<string, vector<string>> getm2() { return m2; } 
    map<string, vector<string>> getm3() { return m3; }
}

class B {
   B() { }
   static A a;

   map<string, vector<string>> getm1() { return a.m1; }
   map<string, vector<string>> getm2() { return a.m2; } 
   map<string, vector<string>> getm3() { return a.m3; } 
} 

有多次从类B调用这些getter函数。掌握了cpp的中间知识,我知道getter会按值返回整个地图。但是,最好通过引用传递它或使用共享指针指向地图,例如将成员变量m1m2m3存储为shared_ptr<map>

  shared_ptr<map<string, vector<string>>> getm1() { return a.m1; } 

这不是性能问题,编译器会照顾它吗?

在阅读了一些关于返回值优化并稍微理解它之后,编译器可以处理一些优化。这是RVO的一部分吗?

提前谢谢。

2 个答案:

答案 0 :(得分:9)

将引用(&)返回到const地图。

class A
{    
    using Map_type = std::map<std::string, std::vector<std::string>>;

    Map_type m1;
    Map_type m2;
    Map_type m3;

  public:
    const auto& getm1() const { return m1; }
    const auto& getm2() const { return m2; } 
    const auto& getm3() const { return m3; }
};

这将允许调用函数&#34;只读&#34;访问地图而无需支付副本的价格。

对于 C ++ 11 和更低版本,而不是auto作为返回类型,必须在函数上声明返回类型。 此外,using仅适用于C ++ 11及更高版本,但对于早期编译器,必须使用typedef

class A
{    
    typedef std::map<std::string, std::vector<std::string>> Map_type;

    Map_type m1;
    Map_type m2;
    Map_type m3;

  public:
    const Map_type& getm1() const { return m1; }
    const Map_type& getm2() const { return m2; } 
    const Map_type& getm3() const { return m3; }
};

答案 1 :(得分:1)

如果您有内部锁定,则无法在不引起竞争条件的情况下返回对缓存地图的引用。您还需要为调用者提供锁定修改数据的方法。如果它是您正在寻找的效率,那么需要考虑的设计是这样的:

class Cache
{
public:
    using mutex_type   = std::shared_timed_mutex;
    using reading_lock = std::shared_lock<mutex_type>;
    using writing_lock = std::unique_lock<mutex_type>;

    using map_type = std::map<std::string, std::vector<std::string>>;

    reading_lock lock_for_reading() const { return reading_lock{mtx}; }
    writing_lock lock_for_writing()       { return writing_lock{mtx}; }

    map_type const& use() const { return m; }

private:

    void update_map()
    {
        // protect every update with a writing_lock
        auto lock = lock_for_writing();

        // safely update the cached map
        m["wibble"] = {"fee", "fie", "foe", "fum"};
    }

    mutable mutex_type mtx;
    map_type   m = {{"a", {"big", "wig"}}, {"b", {"fluffy", "bunny"}}};

};

int main()
{
    Cache cache;

    { // start a scope just for using the map

        // protect every access with a reading_lock
        auto lock = cache.lock_for_reading();

        // safely use the cached map
        for(auto const& s: cache.use().at("a"))
            std::cout << s << '\n';

    } // the lock is released here

    // ... etc ...
}

通过使调用者可以使用锁定,您可以在数据读取时保护数据免受竞争条件的影响。通过使用read&amp;写入锁定可以获得性能,因为您知道调用者不会修改缓存的数据。

在内部,当您更新缓存的地图时,您需要使用 writing_lock 以确保在更新时没有其他人正在阅读它们。

为了提高效率,您可能需要为每张地图单独mutex,但这取决于您的具体情况。

注意:此解决方案将调用者的责任放在正确锁定数据上。这里建议使用更强大(更复杂)的解决方案:An idea for the GSL to make multithreading code safer,其中包含示例实现:gsl_lockable