2013-07-19 11 views
6

Boost.Protoを使用して、埋め込みドメイン固有言語をEigenライブラリで実装された一連の行列演算に変換したいと考えています。効率が重要なので、私はprotoがEigenの式テンプレートを生成し、早期の評価を避けたい。Boost.Protoで固有表現テンプレートを構築する

私は行列乗算式を生成することができる単純な文法を実装しました。以下のコードは、警告なしでコンパイルします(g ++ 4.8.0およびIntel C++ 2013.3、Boost 1.54.0およびEigen 3.1.3)。私の式は1回の乗算操作しかない限り動作します。チェーンに乗算を追加すると、すぐにクラッシュします。 Valgrindは、Eigen :: GeneralProduct式テンプレートの一時変数の1つが評価が完了する前に破棄されるため、これが原因であることを示しています。

私はこれがなぜ起こるのか、それを防ぐために何ができるのか分かりません。すべての助けに感謝します!

#include <iostream> 

#include <boost/fusion/container.hpp> 
#include <boost/mpl/int.hpp> 
#include <boost/mpl/void.hpp> 
#include <boost/proto/proto.hpp> 
#include <boost/ref.hpp> 
#include <boost/type_traits/remove_const.hpp> 
#include <boost/type_traits/remove_reference.hpp> 
#include <boost/utility.hpp> 

#include <Eigen/Dense> 

namespace fusion = boost::fusion; 
namespace mpl = boost::mpl; 
namespace proto = boost::proto; 

typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> matrix; 

// Placeholders 

const proto::terminal<mpl::int_<0> >::type I1 = {{}}; 
const proto::terminal<mpl::int_<1> >::type I2 = {{}}; 
const proto::terminal<mpl::int_<2> >::type I3 = {{}}; 

// Grammar 

template<class Rule, class Callable = proto::callable> 
struct External : 
    proto::when<Rule, proto::external_transform> {}; 

struct matmul_transform : proto::callable { 
    template<class Sig> struct result; 

    template<class This, class MatrixExpr1, class MatrixExpr2> 
    struct result<This(MatrixExpr1, MatrixExpr2)> { 
      typedef typename Eigen::ProductReturnType< 
        typename boost::remove_const<typename boost::remove_reference<MatrixExpr1>::type>::type, 
        typename boost::remove_const<typename boost::remove_reference<MatrixExpr2>::type>::type>::Type 
        type; 
    }; 

    template<class MatrixExpr1, class MatrixExpr2> 
    typename result<matmul_transform(MatrixExpr1, MatrixExpr2)>::type 
    operator()(const MatrixExpr1 &a, const MatrixExpr2 &b) const { 
      return a * b; 
    } 
}; 


struct MatmulGrammar; 

struct InputPlaceholder : proto::terminal<proto::_> {}; 

struct MatrixMultiplication : 
    proto::multiplies<MatmulGrammar, MatmulGrammar> {}; 

struct MatmulGrammar : proto::or_< 
    External<InputPlaceholder>, 
    External<MatrixMultiplication> > {}; 

struct matmul_transforms : proto::external_transforms< 
    proto::when<MatrixMultiplication, matmul_transform(MatmulGrammar(proto::_left), MatmulGrammar(proto::_right))>, 
    proto::when<InputPlaceholder, proto::functional::at(proto::_data, proto::_value)> > {}; 

int main() { 
    matrix mat1(2,2), mat2(2,2), mat3(2,2), result(2,2); 

    mat1 << 1, 2, 3, 4; 
    mat2 << 5, 6, 7, 8; 
    mat3 << 1, 3, 6, 9; 

    MatmulGrammar mmg; 

    // THIS WORKS: 
    result = mmg(I1 * I2, 
      mpl::void_(), 
      (proto::data = fusion::make_vector(boost::cref(mat1), boost::cref(mat2), boost::cref(mat3)), 
      proto::transforms = matmul_transforms())); 

    std::cout << result << std::endl; 

    // THIS CRASHES: 
    result = mmg(I1 * I2 * I3, 
      mpl::void_(), 
      (proto::data = fusion::make_vector(boost::cref(mat1), boost::cref(mat2), boost::cref(mat3)), 
      proto::transforms = matmul_transforms())); 

    std::cout << result << std::endl; 

    return 0; 
} 
+3

プロトに固有の使い方について今、この年のC++での話がありました。私が正しく覚えていれば、彼らはこの問題について明示的に話し、解決方法を説明しました。ここの講演ビデオ[http](http://github.com/boostcon/cppnow_presentations_2013/blob/master/fri/proto-eigen-fem.pdf?raw=true) ://www.youtube.com/watch?v = pDTQlwXkjvU)とソースコード[ここ](https://github.com/barche/eigen-proto)をご覧ください。残念ながら、オーディオは直前の分に欠落していますが、それまではすべて正常に動作します。 – llonesmiz

答えて

3

これは、コメントにリンクされたソリューションとあなたのアプローチをマージする私の試みです。 stored_result_expressiondo_wrap_expressionwrap_expressionhereからコピーしました。あなたのコードまたは話したものの変更は、//CHANGEDとマークされています。

#include <iostream> 

#include <boost/fusion/container.hpp> 
#include <boost/mpl/int.hpp> 
#include <boost/mpl/void.hpp> 
#include <boost/proto/proto.hpp> 
#include <boost/ref.hpp> 
#include <boost/type_traits/remove_const.hpp> 
#include <boost/type_traits/remove_reference.hpp> 
#include <boost/utility.hpp> 

#include <Eigen/Dense> 

namespace fusion = boost::fusion; 
namespace mpl = boost::mpl; 
namespace proto = boost::proto; 

typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> matrix; 

// Placeholders 

const proto::terminal<mpl::int_<0> >::type I1 = {{}}; 
const proto::terminal<mpl::int_<1> >::type I2 = {{}}; 
const proto::terminal<mpl::int_<2> >::type I3 = {{}}; 

// Grammar 

template<class Rule, class Callable = proto::callable> 
struct External : 
    proto::when<Rule, proto::external_transform> {}; 

struct matmul_transform : proto::callable { 
    template<class Sig> struct result; 

    template<class This, class Expr, class MatrixExpr1, class MatrixExpr2> 
    struct result<This(Expr, MatrixExpr1, MatrixExpr2)> { 
      typedef typename Eigen::MatrixBase< 
         typename Eigen::ProductReturnType< 
          typename boost::remove_const<typename boost::remove_reference<MatrixExpr1>::type>::type, 
          typename boost::remove_const<typename boost::remove_reference<MatrixExpr2>::type>::type 
         >::Type 
        >::PlainObject& 
        type; //CHANGED - THIS IS THE TYPE THAT IS USED IN THE CODE OF THE TALK 
    }; 

    template<class Expr, class MatrixExpr1, class MatrixExpr2> 
    typename result<matmul_transform(Expr, MatrixExpr1, MatrixExpr2)>::type 
    operator()(Expr& expr, const MatrixExpr1 &a, const MatrixExpr2 &b) const { //CHANGED - ADDED THE expr PARAMETER 
      expr.value = a*b; 
      return expr.value; 
    } 
}; 


struct MatmulGrammar; 

struct InputPlaceholder : proto::terminal<proto::_> {}; 

struct MatrixMultiplication : 
    proto::multiplies<MatmulGrammar, MatmulGrammar> {}; 

struct MatmulGrammar : proto::or_< 
    External<InputPlaceholder>, 
    External<MatrixMultiplication> > {}; 

struct matmul_transforms : proto::external_transforms< 
    proto::when<MatrixMultiplication, matmul_transform(proto::_, MatmulGrammar(proto::_left), MatmulGrammar(proto::_right))>, //CHANGED - ADAPTED TO THE NEW SIGNATURE OF matmul_transform 
    proto::when<InputPlaceholder, proto::functional::at(proto::_data, proto::_value)> > {}; 

// THE FOLLOWING CODE BLOCK IS COPIED FROM https://github.com/barche/eigen-proto/blob/master/eigen_calculator_solution.cpp 
//---------------------------------------------------------------------------------------------- 
/// Wraps a given expression, so the value that it represents can be stored inside the expression itself 
template<typename ExprT, typename ValueT> 
struct stored_result_expression : 
    proto::extends< ExprT, stored_result_expression<ExprT, ValueT> > 
{ 
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW 

    typedef proto::extends< ExprT, stored_result_expression<ExprT, ValueT> > base_type; 

    explicit stored_result_expression(ExprT const &expr = ExprT()) 
    : base_type(expr) 
    { 
    } 

    /// Temporary storage for the result of the expression 
    mutable ValueT value; 
}; 

struct do_wrap_expression : proto::transform<do_wrap_expression> 
{ 
    template<typename ExprT, typename StateT, typename DataT> 
    struct impl : proto::transform_impl<ExprT, StateT, DataT> 
    { 
    typedef typename boost::result_of<MatmulGrammar(ExprT, StateT, DataT)>::type result_ref_type; //CHANGED - TO USE YOUR GRAMMAR 
    typedef typename boost::remove_reference<result_ref_type>::type value_type; 
    typedef typename boost::remove_const<typename boost::remove_reference<ExprT>::type>::type expr_val_type; 
    typedef stored_result_expression<expr_val_type, value_type> result_type; 

    result_type operator()(typename impl::expr_param expr, typename impl::state_param state, typename impl::data_param data) 
    { 
     return result_type(expr); 
    } 
    }; 
}; 

/// Wrap multiplies expressions so they can store a temporary result 
struct wrap_expression : 
    proto::or_ 
    < 
    proto::terminal<proto::_>, 
    proto::when 
    < 
     proto::multiplies<proto::_, proto::_>, 
     do_wrap_expression(
     proto::functional::make_multiplies 
     (
      wrap_expression(proto::_left), wrap_expression(proto::_right) 
     ), 
     proto::_state, //CHANGED - THESE EXTRA PARAMETERS ARE NEEDED TO CALCULATE result_ref_type IN do_wrap_expression 
     proto::_env 
    ) 
    >, 
    proto::nary_expr< proto::_, proto::vararg<wrap_expression> > 
    > 
{ 
}; 
//-------------------------------------------------------------------------------------------------- 

int main() { 
    matrix mat1(2,2), mat2(2,2), mat3(2,2), result(2,2); 

    mat1 << 1, 1, 0, 1; 
    mat2 << 1, 1, 0, 1; 
    mat3 << 1, 1, 0, 1; 

    MatmulGrammar mmg; 
    wrap_expression wrap; 

    //THIS WORKS: 
    result = mmg(//THIS IS REALLY HORRIBLE, BUT IT WORKS. IT SHOULD PROBABLY BE HIDDEN BEHIND A FUNCTION 
       wrap(
        I1 * I2, 
        mpl::void_(), 
        (proto::data = fusion::make_vector(boost::cref(mat1), boost::cref(mat2), boost::cref(mat3)), 
         proto::transforms = matmul_transforms()) 
       ), 
       mpl::void_(), 
       (proto::data = fusion::make_vector(boost::cref(mat1), boost::cref(mat2), boost::cref(mat3)), 
        proto::transforms = matmul_transforms()) 
      ); 

    std::cout << result << std::endl; 

    // THIS DOESN'T CRASH ANYMORE: 
    result = mmg(
       wrap(
        I1 * I2 * I3 * I1 * I2 * I3, 
        mpl::void_(), 
        (proto::data = fusion::make_vector(boost::cref(mat1), boost::cref(mat2), boost::cref(mat3)), 
         proto::transforms = matmul_transforms()) 
       ), 
       mpl::void_(), 
       (proto::data = fusion::make_vector(boost::cref(mat1), boost::cref(mat2), boost::cref(mat3)), 
        proto::transforms = matmul_transforms()) 
      ); 

    std::cout << result << std::endl; 

    return 0; 
} 
+1

便利なリンクと私のコードを編集する時間を取ってくれてありがとう!あなたの提案は、私にはかなり邪魔になりました!私がC++の解説についてはあまり好きではないことは、評価を強制し、すべてのマトリックス製品操作で一時的に作成するということです。 stored_result_expressionsの完全に事前計算された行列ではなく、式テンプレートオブジェクトへの共有ポインタを格納することでこれを避けることができたと思います。もちろん、それが実際にベンチマークなしで効率的であると言うのは難しいです。 – chardmeier

3

ここにもう一つの解決策があります。 protoの式オブジェクトに干渉するのではなく、中間のEigenオブジェクトへの共有ポインタを状態の一部として保存します。さて話をC++に触発されたソリューションと比較すると、それは次のような利点があります

  • それは固有の式テンプレートの早期評価を強制するものではありません。
  • 文法の変更はそれほど必要ではないため、ドメイン固有の言語の構文にあまり影響を与えません。
  • 中間オブジェクトを生きたままにする責任は、おそらくそれが所属する状態であるとみなされます。特に、文法はスレッドセーフであると考えています(protoの場合)。
  • 行列ではなく式テンプレートを返します。このテンプレートを変数に格納しておき、すべての部分が含まれているので、余暇で後で評価しても安全です。

欠点:

  • 代わりにきちんとした行列を返す、あなたはあなたが実際に興味を持っている部分を抽出するために持っているから、扱いにくい構造を得る
  • 一時オブジェクトが上の割り当てられています。スタックの代わりにヒープ。
  • 好きな場合は、マトリックスに共有ポインタを提供する必要があります。

#include <iostream> 

#include <boost/fusion/include/container.hpp> 
#include <boost/fusion/include/join.hpp> 
#include <boost/make_shared.hpp> 
#include <boost/mpl/int.hpp> 
#include <boost/mpl/void.hpp> 
#include <boost/proto/proto.hpp> 
#include <boost/ref.hpp> 
#include <boost/shared_ptr.hpp> 
#include <boost/type_traits/remove_const.hpp> 
#include <boost/type_traits/remove_reference.hpp> 

#include <Eigen/Dense> 

namespace fusion = boost::fusion; 
namespace mpl = boost::mpl; 
namespace proto = boost::proto; 

typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> matrix; 

// Placeholders 

const proto::terminal<mpl::int_<0> >::type I1 = {{}}; 
const proto::terminal<mpl::int_<1> >::type I2 = {{}}; 
const proto::terminal<mpl::int_<2> >::type I3 = {{}}; 

// Grammar 

template<class Rule, class Callable = proto::callable> 
struct External : 
    proto::when<Rule, proto::external_transform> {}; 

struct matmul_transform : proto::callable { 
    template<class Sig> struct result; 

    template<class This, class ExprList1, class ExprList2> 
    struct result<This(ExprList1, ExprList2)> { 
      typedef typename boost::remove_reference< 
        typename fusion::result_of::front<ExprList1>::type>::type::element_type M1; 
      typedef typename boost::remove_reference< 
        typename fusion::result_of::front<ExprList2>::type>::type::element_type M2; 
      typedef typename Eigen::ProductReturnType< 
        typename boost::remove_const<typename boost::remove_reference<M1>::type>::type, 
        typename boost::remove_const<typename boost::remove_reference<M2>::type>::type>::Type 
        product_return_type; 
      typedef typename fusion::result_of::push_front< 
          const typename fusion::result_of::join<const ExprList1, const ExprList2>::type, 
          boost::shared_ptr<product_return_type> >::type 
        type; 

    }; 

    template<class ExprList1, class ExprList2> 
    typename result<matmul_transform(ExprList1, ExprList2)>::type 
    operator()(const ExprList1 &a, const ExprList2 &b) const { 
      typedef typename result<matmul_transform(ExprList1, ExprList2)>::product_return_type product_return_type; 
      return push_front(join(a, b), boost::make_shared<product_return_type>(*front(a) * *front(b))); 
    } 
}; 

struct placeholder_transform : proto::callable { 
    template<class Sig> struct result; 

    template<class This, class Data, class Value> 
    struct result<This(Data, Value)> { 
      typedef typename boost::remove_const<typename boost::remove_reference< 
        typename fusion::result_of::at<Data, typename boost::remove_reference<Value>::type>::type> 
          ::type>::type ptr_type; 
      typedef typename fusion::list<ptr_type> type; 
    }; 

    template<class Data, class Value> 
    typename result<placeholder_transform(Data, Value)>::type 
    operator()(Data &data, Value value) const { 
      return fusion::make_list(fusion::at<Value>(data)); 
    } 
}; 

struct MatmulGrammar; 

struct InputPlaceholder : proto::terminal<proto::_> {}; 

struct MatrixMultiplication : 
    proto::multiplies<MatmulGrammar, MatmulGrammar> {}; 

struct MatmulGrammar : proto::or_< 
    External<InputPlaceholder>, 
    External<MatrixMultiplication> > {}; 

struct matmul_transforms : proto::external_transforms< 
    proto::when<MatrixMultiplication, matmul_transform(MatmulGrammar(proto::_left), MatmulGrammar(proto::_right))>, 
    proto::when<InputPlaceholder, placeholder_transform(proto::_data, proto::_value)> > {}; 

int main() { 
    boost::shared_ptr<matrix> mat1 = boost::make_shared<matrix>(2,2); 
    boost::shared_ptr<matrix> mat2 = boost::make_shared<matrix>(2,2); 
    boost::shared_ptr<matrix> mat3 = boost::make_shared<matrix>(2,2); 
    boost::shared_ptr<matrix> result = boost::make_shared<matrix>(2,2); 

    *mat1 << 1, 1, 0, 1; 
    *mat2 << 1, 1, 0, 1; 
    *mat3 << 1, 1, 0, 1; 

    MatmulGrammar mmg; 

    // THIS WORKS: 
    *result = *front(
      mmg(I1 * I2, mpl::void_(), 
      (proto::data = fusion::make_vector(mat1, mat2, mat3), 
      proto::transforms = matmul_transforms()))); 

    std::cout << *result << std::endl; 

    // THIS WORKS, TOO: 
    *result = *front(
      mmg(I1 * I2 * I3 * I3 * I2 * I1, mpl::void_(), 
      (proto::data = fusion::make_vector(mat1, mat2, mat3), 
      proto::transforms = matmul_transforms()))); 

    std::cout << *result << std::endl; 

    return 0; 
} 
関連する問題