• Keine Ergebnisse gefunden

3.6 Type construction and generators

The intention of programming with layers is to offer a greater flexibility for the user of data structures, to avoid the redundancy of code (as well as that of computer memory) and to always have the most efficient implementation for a special purpose.

However, for users that are not familiar with mixin layers and the special concept of layer assembly it might be cumbersome to construct the type they want to use.

For this reason, we would like to have atype generator, that can automatically assemble a desired type. Before we introduce this template metaprogramming construct, we would like to describe how types are assembled manually.

3.6.1 Rules of type construction

The type construction underlies certain rules and restrictions, since not every layer combination makes sense, although it would be technically possible. In the following, we would like to give an outline of these rules and implications.

Rules concerning the order of assembly

Besides starting with the Baselayer, we have the following precedence rules:

• First of all it should be stated, that the dimension layers are so essential that they should be included immediately after the Base layer. Which dimension layer comes first is however unimportant.

• NeighbourhoodStartsLayer,EdgeTargetsLayer,WeightValuesLayerandFirstTargetsLayer contain the basic data of a matrix type and should be used directly after the dimension layers.

In any case, they must be used prior to any of the other layers, since they usually depend on the data provided here.

• The EdgeLayerrequires theNeighbourhoodStartsLayerand theEdgeTargetsLayer.

• TheWeightedEdgeLayerrequires theNeighbourhoodStartsLayer, theEdgeTargetsLayerand theWeightValuesLayer.

• The (sparse) SourceNodesLayerrequires the NeighbourhoodStartsLayerand the EdgeTargetsLayer.

• The (sparse)RowWiseLayerandColumnWiseLayerrequire theNeighbourhoodStartsLayer, the EdgeTargetsLayerand the WeightValuesLayer.

• TheConsecutiveSourceNodesLayerrequires theNeighbourhoodStartsLayerand the FirstTargetsLayer.

• TheConsecutiveRowWiseLayerand ConsecutiveColumnWiseLayerrequire the NeighbourhoodStartsLayer, theFirstTargetsLayerand theWeightValuesLayer.

• TheRandomAccessLayerrequires exactly one of RowWiseLayer,ColumnWiseLayer, ConsecutiveRowWiseLayerorConsecutiveColumnWiseLayer.

• TheSliceLayersyntactically doesn’t need any other layer although its data refer to the weighted edges stored in theWeightValuesLayer.

• TheDiagonalLayerneeds the SliceLayer.

Rules concerning the combination of layers

• The FirstTargetsLayer and the EdgeTargetsLayers must be used mutually exclusive. The edge targets sequence only makes sense for matrices, where the edge targets numbering is not consecutive (a matrix with this property is usually called sparse).

• Also, when adding an orientation to the matrix type, one has to choose between either the RowWiseLayeror theColumnWiseLayer. Using both, (although technically possible) would lead to overwriting and shadowing the types (and functions) of the first layer by the second layer in the hierarchy, disabling the first layer’s functionality.

• Accordingly, theConsecutiveRowWiseLayerand theConsecutiveColumnWiseLayerare mutu-ally exclusive.

• Of course, the layers designed for sparse matrices don’t make any sense in a dense matrix type, and vice versa.

3.6.2 Type examples

In order to illustrate the rules of the type construction, we give a few example types. First, a simple sparse matrix that offer a row wise access to its data.

Sparse matrix type

typedef DataSequence<unsigned int,unsigned int> IVT;

typedef DataSequence<double,unsigned int> WVT;

typedef NormalConfig<unsigned int,Value> DimConfig;

typedef NormalConfig<IVT,Value> IndexConfig;

typedef WeightConfig<WVT,Value,Value> WeightValuesConfig;

typedef VerticalDimensionLayer<Base,DimConfig> VDLayer;

typedef HorizontalDimensionLayer<VDLayer,DimConfig> DLayer;

typedef NeighbourhoodStartsLayer<DLayer,IndexConfig> ETSLayer;

typedef EdgeTargetsLayer<ETSLayer,IndexConfig> ETLayer;

typedef WeightValuesLayer<ETLayer,WeightValuesConfig> WVLayer;

typedef RowWiseLayer<WVLayer> RWLayer;

typedef Final<RWLayer>::Type RowWiseSparseMatrix;

Now we show, how a row wise oriented dense matrix type with random access can be assembled.

Dense matrix type

typedef FunctionSequence<linear<unsigned int,unsigned int> > LST;

typedef FixedValueSequence<unsigned int,unsigned int,0> FST;

typedef DataSequence<double,unsigned int> WVT;

typedef NormalConfig<unsigned int,Value> DimConfig;

typedef NormalConfig<LST,Value> LinearConfig;

typedef NormalConfig<FST,Value> FirstZeroConfig;

typedef WeightConfig<WVT,Value,Value> WeightValuesConfig;

typedef VerticalDimensionLayer<Base,DimConfig> VDLayer;

typedef HorizontalDimensionLayer<VDLayer,DimConfig> DLayer;

typedef NeighbourhoodStartsLayer<DLayer,LinearConfig> DenseETSLayer;

typedef FirstTargetsLayer<DenseETSLayer,FirstZeroConfig> DenseFTLayer;

typedef WeightValuesLayer<DenseFTLayer,WeightValuesConfig> DenseWVLayer;

typedef ConsecutiveSourcesNodesLayer<DenseWVLayer> DenseSNLayer;

typedef ConsecutiveRowWiseLayer<DenseSNLayer> DenseRWLayer;

typedef RandomAccessLayer<DenseRWLayer> DenseRALayer;

typedef Final<DenseRALayer>::Type DenseMatrix;

Such a type as the above assembled dense matrix type is reasonable, if we don’t know the size of the matrix at compile time (i.e. we would like to be able to change it at run time). In the other case, if the matrix size is completely fixed at compile time, we would have wasted memory for the edge target starts and first target sequences and the dimensions for each object. Especially when storing large amounts of such objects this would lead to a great memory overhead.

A solution is again closely related to the singleton design pattern (cf. [GHJV94]). Those properties that are common to all objects of a type should also be instantiated only once. This is achieved by declaring the according data as static(see also the treatment of fixed values in Section 3.3.3).

As an example, we want to construct a 3×3 dense matrix type. First, we replace the type DataSequencewithFixedLengthDataSequence, leaving the rest unchanged:

A fixed length for the weight values sequence

#define DIM 3

typedef FixedLengthDataSequence<double,unsigned int,DIM*DIM> WVT;

We do this in order to have all the data ”owned” by an object of the according type. The DataSequencetype only includes a size variable and a pointer to a memory block in the heap. This however could turn out badly when applying an algorithm to a very long sequence of such matrix objects. An example for this kind of algorithm is the matrix-vector multiplication for block matrices as they appear in Chapter 9. TheDataSequencetype then requires not only to store the pure matrix entries (in this case 9doubles) but also a pointer.

Furthermore, dereferencing this pointer would lead to an address somewhere in the heap, not necessarily directly after the preceding matrix object. In other words, we would have a fragmented memory that e.g. wouldn’t benefit from the pipelining techniques of modern microprocessors, and it would mislead any caching strategy.

Another advantageous side effect is that the default constructor of FixedLengthDataSequence already constructs the object with the correct size, which is extremely comfortable when allocating an array of such objects.

When determining the size in bytes of this data type, with e.g.

int main(void) {

cout << "sizeof(double) : " << sizeof(double) << endl;

cout << "sizeof(WVT) : " << sizeof(WVT) << endl;

cout << "sizeof(DenseMatrix) : " << sizeof(DenseMatrix) << endl;

}

we get the output

sizeof(double) : 8

sizeof(WVT) : 72

sizeof(DenseMatrix) : 92

on a Linux 32 bit Pentium 4 machine. Now we aim to reduce the size of the dense matrix type to the size of the pure data array WVT which is 9 times the size of a double precision floating point variable. We use the following defnitions:

Fixed size matrix type

typedef FixedLengthStaticFunctionSequence<fixed_linear<unsigned int, unsigned int, unsigned int, DIM>,unsigned int,DIM+1> > FLST;

typedef FixedValueFixedLengthSequence<unsigned int,

unsigned int,0,DIM> FZST;

typedef FixedConfig<unsigned int,DIM> FDimConfig;

typedef NormalConfig<FLST,Static> FLinearConfig;

typedef NormalConfig<FZST,Static> FFirstZeroConfig;

typedef WeightConfig<WVT,Value,Value> FLengthValueConfig;

typedef VerticalDimensionLayer<Base,FDimConfig> FixedVDLayer;

typedef HorizontalDimensionLayer<FixedVDLayer,FDimConfig> FixedDLayer;

typedef NeighbourhoodStartsLayer<FixedDLayer,FLinearConfig> FixedETSLayer;

typedef FirstTargetsLayer<FixedETSLayer,FFirstZeroConfig> FixedFTLayer;

typedef WeightValuesLayer<FixedFTLayer,FLengthValueConfig> FixedWVLayer;

typedef ConsecutiveSourcesNodesLayer<FixedWVLayer> FixedSNLayer;

typedef ConsecutiveRowWiseLayer<FixedSNLayer> FixedRWLayer;

typedef RandomAccessLayer<FixedWESLayer> FixedRALayer;

typedef Final<FixedRALayer>::Type FixedMatrix;

Now thisFixedMatrixtype has the desired property, and an according program yields:

sizeof(FixedMatrix) : 72

Specifying sizes at compile time also has other advantages. Generic algorithms can determine (with traits – see Section 3.5 – at compile-time!) which data structure they have have to deal with and then dispatch a specialized variant of the algorithm (see e.g. Section 3.7.3).

3.6.3 A simple type generator

As we have seen in the previous examples, for constructing a type, it is necessary to

• define appropriate sequences,

• define appropriate config structs,

• and pass the config structs to the according layers and assemble them in the correct order.

This might seem as a circuituos way to gain a type, however, since we have so many possible combi-nations, we must somehow offer this functionality to the user. The philosophy is not so much

”The required sequences are a consequence of the properties of the desired type.”

but rather

”The resulting type is a consequence of the utilized sequences.”

Therefore, at the moment, we only supply a low level type generator, that merely relieves the user of caring about the order of assemby.

Low level matrix type generator template <class VerticalDimensionConfig, // 1.

class HorizontalDimensionConfig, // 2.

class NeighbourhoodStartsConfig, // 3.

class FirstTargetsConfig, // 4.

class EdgeTargetsConfig, // 5.

class EdgeConfig, // 6.

class WeightValuesConfig, // 7.

class WeightedEdgeConfig, // 8.

class ConsecutiveSourceNodesConfig, // 9.

class ConsecutiveRowWiseConfig, // 10.

class ConsecutiveColumnWiseConfig, // 11.

class SourceNodesConfig, // 12.

class RowWiseConfig, // 13.

class ColumnWiseConfig, // 14.

class RandomAccessConfig, // 15.

class AssignmentConfig> // 16.

struct LowLevelMatrixGenerator {

typedef Base Layer0;

//...

typedef typename Final<Layer16>::Type type;

};

The typedef’s in between have the form

typedef typename IfElse<Equal<LayerNameConfig,EmptyType>::value, Layern,

LayerNameLayer<Layern,LayerNameConfig> >::type Layern+1; if the layer LayerName has an own storage, and

typedef typename IfElse<Equal<LayerNameConfig,EmptyType>::value, Layern,

LayerNameLayer<Layern> >::type Layern+1;

if it doesn’t store own data. The syntactic usage of the generator is based on specifying the config structs, for the layers we want to include, as template arguments. If a template parameter is equal to EmptyType, the according layer is not inluded.

This generator is intended to offer the greatest flexibility for the type construction. For a more user-friendly type generation, one can add specialized generators, e.g. a sparse matrix type generator, a dense matrix type generator etc.

For the gcc 3.4.1 however, we experienced problems when using the above generator for block matrix types. The generator was used to build a dense matrix type with fixed dimensions, and this type was used as an entry type for generating a block sparse matrix. The latter however failed, the compiler crashed. Since increasing the maximum instantiation depth for template classes with the switch-ftemplate-depth-n also didn’t fix the problem, we used the manual type construction from Section 3.6.2 as a workaround in this case.