For example, considering v
and w
to be dense vectors, the expression:
plus_assign< blas > (v, scalar_vector_mult( 3.0,w ) ) ;will be dispatched to a
daxpy
call in the BLAS backend.
On the other hand the expression:
assign< blas > (v, scalar_vector_mult( 3.0, w ) ) ;has no direct blas equivalent. The expression comes down to a
daxpy
call but first all elements of v
need to be set to zero by means
of a dscal
or a memset
for instance.
Thus the blas backend might be unable to evaluate the expression and
the expression will than be dispatched to the
generic glas backend instead. On the other hand a more intelligent blas
backend might accept the expression and evaluate the expression using any
of the mechanisms described above. Depending on the context, calling
BLAS twice might be more approriate (performance-wise,
memory-wise,...) as using the glas backend or not. That is exactly the reason
for supporting multiple backends, each relying on different third-party libraries
and implementing different policies.
A lot of intelligence can be put into these backends. Backends can e.g. be provided that provide optimal performance given a set of third-party libraries and type of hardware. Additionally a default-backend can be configured which will be called if no backend is specified in the assignment for maximal transparancy.
During the development of an application the glas-interface can be used without taking third-party libraries that might be available into account. Once the application need to be compiled for a given target platform, a backend can be provided that intelligently dispatches the most frequently used expressions to the available third-party libraries. On many platforms a vendor-tuned BLAS library is available but many vendor-tuned libraries also provide additional functionality. ACML provides fast-math routines, MKL provides sparse solvers and VML etc.
The free function assign is defined as follows:
template <class BackEnd, class X, class E> void assign( X& x, E const& e ) { assign_backend_selector<BackEnd, X, E>() ( x, e ) ; }The assign function only serves to easily create the
assign_backend_selector
functor. The reason for calling this functor is to be able to partially specialise the functor
in the different backend on the expressions passed in the assign function.
assign_backend_selector
can
be fully or partially specialised on specific expressions. Each of these
specialisations can then recover the data from the expression-object and
call the corresponding BLAS function.
assign(y, scalar_vector_mult(norm_2(v),w)) ;is equivalent to the loop:
norm_2_expression<V> norm_2_e( norm_2(v) ) ; for ( size_type i=0; i<w.size(); ++i ) { y(i) = norm_2_e() * w(i) ; }This implies that norm_2(v) is not stored in a temporary but evaluated for each i. This is something that we usually do not want. There are two workarounds: first, explicitly store norm_2(v) in a temporary:
s = norm_2(v) ; assign(y, scalar_vector_mult(s,w) ) ;or use a backend that does this for you.
s
.
The most generic way is the assign function:
assign( s, glas::norm_1(v) ) ; assign<backend::blas>( s, glas::norm_1(v) ) ;assign assumes that the result_type of glas::norm_1(v) can be implicitly converted to s. For example,
dense_vector<int> v(5) ; std::complex<double> s ; assign( s, glas::norm_1(v) ) ; assign<backend::blas>( s, glas::norm_1(v) ) ;works, since int can be converted to complex<double>.
A more intuitive way is to use conversion to a scalar type:
s = glas::evaluate( glas::norm_1(v) ) ; s = glas::evaluate<backend_blas>( glas::norm_1(v) ) ;evaluate assumes that the result_type of glas::norm_1(v) can be implicitly converted to s.
The shortest syntax is:
s = glas::norm_1(v) ;where it is assumed that norm_1_expression<V> can be converted to s. This condition is stricter than the use of assign or evaluate. In the case that implicit conversion is not possible, assign or evaluate should be used. For example,
dense_vector<int> v(5) ; std::complex<double> s ; s = glas::norm_1(v) ) ;does not work: the conversion from norm_1_expression< dense_vector<int> > to int is possible, or to double as well, since this requires at most two implicit conversions. However, the conversion to std::complex<double> requires more than two implicit conversions and C++ does not allows this.
The nice thing about conversion is that GLAS objects can be assigned to non GLAS objects. In order to do that, one should specialize the glas::backend::glas::assign_functor for the left hand side object type. For example, we could assign glas::dense_vector<T> to std::vector<T>.