缓存重复输入数据的表达式评估

时间:2015-05-28 10:11:37

标签: c++ caching numeric

我有几个简单的“表达式”类派生自

namespace Expression {
class Virtual {
  public:
  Virtual();
  ~Virtual();

  virtual
  double eval(double x, double y, double z) const = 0; 
}
}

例如

class MyFun: public Virtual {
  public:
  MyFun();
  ~MyFun();

  virtual
  double eval(double x, double y, double z) const
  {
      return sin(x*x) + exp(z)*y + 3.0; // some expensive expression
  } 
}

这些表达式在eval中很多,但是非常具体的xyz,即网格的节点

class MyMesh {
  MyMesh();
  ~MyMesh();

  virtual
  std::vector<std::vector<double> > getNodes() const
  {
      return allMyNodes_;
  } 
}

被调用者将实例化一个网格,实例化Expression,然后继续进行一些数值过程,其中可能多次在网格节点中评估Expression

MyMesh myMesh();
MyFun myExpr();

Result result = numericalProcedure(myMesh, myExpr);
// [...]

由于eval很贵,我想到了如何加快这一点。我想到的是缓存。

一个想法是将所有eval结果存储在带有索引的向量中的所有网格节点中,我想知道如何最好地实现它。我不想在Expression中粘贴任何网格数据(以保持缓存),我也不想让接口的接口更复杂。

是否存在与此用例匹配的缓存设计模式?什么是将缓存逻辑与其余代码隔离开来的方法?

3 个答案:

答案 0 :(得分:1)

您可以将参数数组中的地图存储到仿函数中的结果中:

class MyFun {
public:
    virtual
    double eval(double x, double y, double z) const
    {
        std::array<double,3> arr {x,y,z};

        //is the answer cached?
        if (cache_.count(arr))
        {
            return cache_[arr];
        }

        double ret = sin(x*x) + exp(z)*y + 3.0;
        //cache the result
        cache_[arr] = ret;
        return ret;
    } 

private:
    //need mutable so it can be modified by a const function
    mutable std::map<std::array<double,3>, double> cache_;
};

您甚至可以在基类中进行缓存,然后将评估转发给虚函数:

class BaseFun {
public:
    double eval(double x, double y, double z) const
    {
        std::array<double,3> arr {x,y,z};
        if (cache_.count(arr))
        {
            return cache_[arr];
        }

        double ret = doEval(x,y,z);
        cache_[arr] = ret;
        return ret;
    }   

protected:
    virtual double doEval (double x, double y, double z) const = 0;
private:
    mutable std::map<std::array<double,3>, double> cache_;
};

class MyFun : public BaseFun {
private:
    virtual double doEval (double x, double y, double z) const override
    {
        return sin(x*x) + exp(z)*y + 3.0;
    }
};

答案 1 :(得分:1)

我会实现一个新的Expression::Virtual子类,它只进行缓存,同时保留另一个表达式的实例来委托实际的计算:

class CachedExpression: public Expression::Virtual {
    private:
       // for simplicity I assume a separate Point class
       mutable std::map<Point, double> cache_; 
       const Expression::Virtual* expr_; // or better auto_ptr or friends
    public: 
       explicit CachedExpression(const Expression::Virtual* expr): cache_(), expr_(expr) {}
       virtual double eval(const Point& point) const {
           if (cache_.find(point) == cache_.end())
               cache_[point] = expr_.eval(point);
           return cache_[point];
       }
};

...
MyMesh myMesh;
MyFun myExpr;
CachedExpression myCached(&myExpr);
// or even CachedExpression myCached(new myFun());

Result result = numericalProcedure(myMesh, myCached);

通过这种方式,您始终可以仅使用myExpr而不是myCached来关闭缓存,或者在不同的{{保留仅N最后查询以保存内存)中实施不同的缓存策略{1}} - 类似课程,并根据您的需要使用。

答案 2 :(得分:1)

  

将缓存逻辑与其余代码隔离的方法是什么?

您可以使用相同的接口将缓存分成另一个类,这可以缓存任何表达式的结果:

class Cache : public Virtual {
public:
    explicit Cache(Virtual const & target) : target(target) {}

    double eval(double x, double y, double z) const override {
       std::array<double,3> key {x,y,z};
       auto cached = cache.find(key);
       if (cached == cache.end()) {
           double result = target(x,y,z);
           cache[key] = result;
           return result;
       } else {
           return cached->second;
       }
    }

private:
    Virtual const & target;
    std::map<std::array<double,3>, double> cache;
};

可以这样使用

Result result = numericalProcedure(myMesh, Cache(myExpr));

在现有表达式周围包装临时缓存。

(如果你想使用一个更永久的缓存,那么它应该有一个失效策略来阻止它变得太大。我的简单例子只在破坏时释放内存,所以如果它永远不会成为内存泄漏破坏。)

  

是否存在与此用例匹配的缓存设计模式?

如果您想为其命名,那就是Proxy pattern的一个例子。