In this section, we describe the different types of Engines and illustrate their creation, when appropriate. First, we describe Engines that explicitly store values and then Engines that compute values. See Table 6-1.
Table 6-1. Types of Engines
Engine tag | description |
---|---|
Engines That Store | |
Brick | explicitly stores all values; similar to C arrays. |
CompressibleBrick | stores all values, reducing storage requirements when all values are identical. |
Dynamic | is a one-dimensional Brick with dynamically resizable domain. This should be used with DynamicArray, not Array. |
Engines That Compute | |
CompFwd | extracts specified components of an engine's vectors, tensors, arrays, etc.; usually created using the comp container function. |
ConstantFunction | makes a scalar value behave like a container. |
IndexFunction<FunctionObject> | makes the FunctionObject's function of indices behave like a container. |
ExpressionTag<Expr> | evaluates an expression tree; usually created by data-parallel expressions. |
Stencil<Function, Expression> | applies a stencil computation (Function) to its input (Expression) which is usually a container; usually created by applying a Stencil object to a container. A stencil computation can use multiple neighboring input values. |
UserFunctionEngine<Function, Expression> | applies the given function (or function object) to its input (Expression) which is usually a container; usually created by applying a UserFunction object to a container. The function implements a one-to-one mapping from its input to values. |
Engines for Distributed Computation | |
MultiPatch<LayoutTag,EngineTag> | runs a separate EngineTag Engine on each context (patch) specified by the given layout. This is the usual Engine for distributed computation. |
Remote<EngineTag> | runs the Engine specified by EngineTag on a specified context. |
Remote<Dynamic> | runs a Dynamic one-dimensional, resizable Engine on a specified context. This is a specialization of Remote. |
Brick Engines explicitly store values just like C arrays. CompressibleBrick Engines optimize their storage requirements when all values are identical. Many Arrays use one of these two Engines. Bricks are the default Engines for Array and Field containers because they explicitly store each value. This explicit storage can require a large amount of space, particularly if all these values are the same. If all a compressible brick Engine's values are identical, the Engine stores that one value rather than many, many copies of the same value. These engines can both save time as well as space. Initializing a compressible engine requires setting only one value, not every value. Using less storage space may also permit more useful values to be stored in cache, improving cache performance. Reading a value in a compressed Engine using the read member function is as fast as reading a value in a Brick Engine, but writing a value always requires executing an additional if conditional. Thus, if an Engine infrequently has multiple different values during its life time, a CompressibleBrick Engine may be faster than a Brick Engine. If an Engine is created and its values are mostly read, not written, a CompressibleBrick Engine may also be faster. Otherwise, a Brick Engine may be preferable. Timing the same program using the two different Engine types will reveal which is faster for a particular situation. In distributed computing, many Engines may have few nonzero values so CompressibleBrick Engines may be preferable. For distributed computing, a container's domain is partitioned into regions each computed by a separate processor and Engine. If the computation is concentrated in sections of the domain, many Engines may have few, if any, nonzero values. Thus, CompressibleBrick Engines may be preferable for distributed computing.
Both Brick and CompressibleBrick Engines have read and operator() member functions taking int and Loc parameters. The parameters should match the Array's dimensionality. For example, if Array a has dimensionality 3, a.read(int, int, int) and a(int, int, int) should be used. The former returns a value that cannot be modified, while the latter can be changed. Using the read member function can lead to faster code. Alternatively, an index can be specified using a Loc. For example, a.read(Loc<3>(1,-2,5)) and a(Loc<3>(1,-2,5)) are equivalent to a.read(1,-2,5)) and a(1,-2,5).
The Dynamic Engine supports changing domain sizes while a program is executing. It is basically a one-dimensional Brick, explicitly storing values, but permitting the number and order of stored values to change. Thus, it supports the same interface as Brick except that all member functions are restricted to their one-dimensional versions. For example, read and operator() take Loc<1> or one int parameter. In addition, the one-dimensional domain can be dynamically resized using create and destroy.