【问题标题】:Caching of expression evaluations for repeated input data缓存重复输入数据的表达式评估
【发布时间】:2015-05-28 19:06:44
【问题描述】:

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

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
  } 
}

这些表达式在许多evaled 中,但非常具体xyzs,即网格的节点

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 结果存储在带有一些索引的向量中的所有网格节点中,我想知道如何最好地实现这一点。我不想在Expressions 中粘贴任何网格数据(将缓存保留在那里),也不想让被调用者的界面更加复杂。

是否有与此用例匹配的缓存设计模式?有哪些方法可以将缓存逻辑与其余代码隔离开来?

【问题讨论】:

  • 你的MyFun实际上是从Virtual派生的吗?您的代码似乎没有这样做。
  • 大概MyMesh myMesh();应该是MyMesh myMesh;,声明一个对象而不是一个返回对象的函数?

标签: c++ caching numeric


【解决方案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;
    }
};

【讨论】:

    【解决方案2】:

    有哪些方法可以将缓存逻辑与其余代码隔离开来?

    您可以将缓存分离到另一个具有相同接口的类中,该类可以缓存任何表达式的结果:

    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的一个例子。

    【讨论】:

      【解决方案3】:

      我将实现一个新的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 来关闭缓存,或者在不同的CachedExpression 类和使用任何你需要的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-08
        • 2010-10-22
        • 2015-06-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-04
        相关资源
        最近更新 更多