2013-07-19 7 views
6

Vorrei utilizzare Boost.Proto per trasformare un linguaggio specifico del dominio incorporato in una serie di operazioni di matrice implementate con la libreria Eigen. Poiché l'efficienza è importante, voglio che proto generi modelli di espressione Eigen ed eviti una valutazione prematura.Creazione di modelli di espressioni Eigen con Boost.Proto

Ho implementato una semplice grammatica che può generare espressioni di moltiplicazione di matrici. Il codice sotto compila senza avvisi (su g ++ 4.8.0 e Intel C++ 2013.3, con Boost 1.54.0 ed Eigen 3.1.3) e funziona fintanto che la mia espressione ha solo una singola operazione di moltiplicazione. Non appena aggiungo più moltiplicazioni alla catena, si blocca. Valgrind mi dice che questo è dovuto al fatto che uno dei provini del modello di espressione Eigen :: GeneralProduct viene distrutto prima che la valutazione sia completata.

Non capisco perché questo accada, o cosa posso fare per impedirlo. Tutto l'aiuto è apprezzato!

#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'era un discorso in questo anni C++ Ora sull'utilizzo Eigen con proto. Se ricordo bene, hanno parlato esplicitamente di questo problema e hanno spiegato come lo hanno risolto. Puoi trovare le diapositive [qui] (https://github.com/boostcon/cppnow_presentations_2013/blob/master/fri/proto-eigen-fem.pdf?raw=true), il video del discorso [qui] (http : //www.youtube.com/watch? v = pDTQlwXkjvU) e il codice sorgente [qui] (https://github.com/barche/eigen-proto) se sei interessato.Sfortunatamente manca l'audio negli ultimi minuti, ma tutto funziona fino ad allora. – llonesmiz

risposta

3

Questo è il mio tentativo di unire il vostro approccio con la soluzione collegata nel commento. Ho copiato stored_result_expression, do_wrap_expression e wrap_expression da here. Le modifiche apportate al codice o a quella del talk sono contrassegnate con //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

Grazie mille per i link utili e per aver dedicato del tempo a modificare il mio codice! I tuoi suggerimenti mi hanno portato un bel po 'sulla strada! Quello che non mi piace della soluzione del linguaggio C++ Now è che impone la valutazione e crea un temporaneo in ogni operazione del prodotto matrice. Penso di essere riuscito ad evitare questo memorizzando i puntatori condivisi agli oggetti del modello di espressione invece delle matrici completamente precompute in stored_result_expressions. Difficile dire se sia effettivamente più efficiente senza benchmark, ovviamente. – chardmeier

3

Ecco un'altra soluzione che sembra funzionare. Invece di interferire con gli oggetti espressione di proto, salva i puntatori condivisi agli oggetti Eigen intermedi come parte dello stato. Rispetto alla soluzione ispirata al talk di C++ Now, presenta i seguenti vantaggi:

  • Non impone la valutazione iniziale dei modelli di espressione di Eigen.
  • Richiede meno modifiche alla grammatica, quindi è meno intrusivo nella sintassi del linguaggio specifico del dominio.
  • La responsabilità di mantenere in vita gli oggetti intermedi è assunta dallo stato, che è probabilmente dove appartiene. In particolare, credo che ciò renda il thread della grammatica sicuro (se il proto è).
  • Restituisce un modello di espressione, non una matrice. Dovresti essere al sicuro anche se conservi questo modello in una variabile e lo valuti in un secondo momento, poiché tutte le parti sono incluse.

Svantaggi:

  • Invece di restituire una matrice ordinata, si ottiene una struttura ingombrante da cui è necessario estrarre la parte che si sta effettivamente interessati a
  • oggetti temporanei vengono assegnate. l'heap invece della pila.
  • Devi fornire puntatori condivisi alle tue matrici se ti piace o no.

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