2013-07-19 8 views
6

Ich möchte Boost.Proto verwenden, um eine eingebettete domänenspezifische Sprache in eine Reihe von Matrixoperationen umzuwandeln, die mit der Eigenbibliothek implementiert werden. Da Effizienz wichtig ist, möchte ich, dass proto Eigenexpressionsvorlagen generiert und eine vorzeitige Auswertung vermeidet.Konstruieren von Eigenexpressionsvorlagen mit Boost.Proto

Ich habe eine einfache Grammatik implementiert, die Matrixmultiplikationsausdrücke erzeugen kann. Der folgende Code kompiliert ohne Warnungen (auf g ++ 4.8.0 und Intel C++ 2013.3, mit Boost 1.54.0 und Eigen 3.1.3) und funktioniert so lange, wie mein Ausdruck nur eine einzige Multiplikation hat. Sobald ich der Kette weitere Multiplikationen hinzufüge, stürzt es ab. Valgrind sagt mir, dass dies daran liegt, dass eines der Expression-Template-Properties von Eigen: GeneralProduct vor der Auswertung zerstört wird.

Ich verstehe nicht, warum das passiert, oder was ich tun kann, um es zu verhindern. Alle Hilfe wird geschätzt!

#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

Es war ein Gespräch in diesem Jahr C++ Jetzt über Eigen mit proto verwenden. Wenn ich mich richtig erinnere, haben sie explizit über dieses Problem gesprochen und erklärt, wie sie es gelöst haben. Sie finden die Folien [hier] (https://github.com/boostcon/cppnow_presentations_2013/blob/master/fri/proto-eigen-fem.pdf?raw=true), das Video des Vortrags [hier] (http: // www : //www.youtube.com/watch? v = pDTQlwXkjvU) und der Quellcode [hier] (https://github.com/barche/eigen-proto) wenn Sie interessiert sind.Leider fehlt in den letzten Minuten der Ton, aber bis dahin läuft alles gut. – llonesmiz

Antwort

3

Dies ist mein Versuch, Ihren Ansatz mit der im Kommentar verlinkten Lösung zusammenzuführen. Ich habe stored_result_expression, do_wrap_expression und wrap_expression von here kopiert. Die Änderungen, die ich entweder an Ihrem Code oder am Telefon vorgenommen habe, sind mit //CHANGED gekennzeichnet.

#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

Vielen Dank für die nützlichen Links und für die Zeit, um meinen Code zu bearbeiten! Deine Vorschläge haben mir einiges gebracht! Was ich an der Lösung von C++ nicht mag, ist, dass es die Evaluierung erzwingt und bei jeder Matrix-Produktoperation ein temporäres erzeugt. Ich denke, dass ich dies vermeiden konnte, indem ich geteilte Zeiger auf die Expression-Template-Objekte anstelle von vollständig vorberechneten Matrizen in stored_result_expressions speicherte. Schwer zu sagen, wenn es ohne Benchmarking natürlich auch effizienter ist. – chardmeier

3

Hier ist eine andere Lösung, die zu funktionieren scheint. Anstatt die Ausdrucksobjekte von proto zu stören, speichert sie gemeinsam genutzte Zeiger auf die dazwischenliegenden Eigen-Objekte als Teil des Zustands. Im Vergleich zu der von C++ inspirierten Lösung hat es nun folgende Vorteile:

  • Es erzwingt keine frühzeitige Auswertung von Eigens Expressionsvorlagen.
  • Es erfordert weniger Änderungen an der Grammatik, so dass es in der Syntax der domänenspezifischen Sprache weniger aufdringlich ist.
  • Die Verantwortung dafür, dass die Zwischenobjekte am Leben bleiben, wird vom Staat übernommen, wo er wohl hingehört. Insbesondere glaube ich, dass dies die Grammatik Thread-sicher macht (wenn Proto ist).
  • Es gibt eine Ausdrucksvorlage zurück, keine Matrix. Sie sollten sicher sein, auch wenn Sie diese Vorlage in einer Variablen speichern und später in Ihrer Freizeit auswerten, da alle Teile enthalten sind.

Nachteile:

  • Statt eine saubere Matrix zurückzukehren, erhalten Sie eine unhandliche Struktur, von dem Sie das Teil extrahieren, die Sie in wirklich interessiert sind
  • temporäre Objekte zugeordnet auf. der Haufen anstelle des Stapels.
  • Sie müssen geteilte Zeiger zu Ihren Matrizen bereitstellen, wenn Sie es mögen oder nicht.

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