Here is a simple seismic wave simulation (wave propagation) based on parallel_for
and blocked_range
. The key parts of this example are shown here; the entire code is available for download.
The main program steps through the simulation of a seismic wave in a core loop that sets the impulse from the source of the disturbance, does the two tough computations of stress updates and velocities, and finally cleans up the edges of the simulation.
First we’ll look at the stress algorithm, in Example 11-2 and Example 11-3. The first shows the serial version of the algorithm, and the second shows an equivalent parallel version.
Example 11-2. Seismic serial: Update stress
static void SerialUpdateStress() { drawing_area drawing(0, 0, UniverseWidth, UniverseHeight); for( int i=1; i<UniverseHeight-1; ++i ) { color_t* c = ColorMap[Material[i]]; drawing.set_pos(1, i); #pragma ivdep for( int j=1; j<UniverseWidth-1; ++j ) { S[i][j] += (V[i][j+1]-V[i][j]); T[i][j] += (V[i+1][j]-V[i][j]); int index = (int)(V[i][j]*(ColorMapSize/2)) + ColorMapSize/2; if( index<0 ) index = 0; if( index>=ColorMapSize ) index = ColorMapSize-1; drawing.put_pixel(c[index]); } } }
Example 11-3. Seismic parallel: Update stress
struct UpdateStressBody { void operator()( const tbb::blocked_range<int>& range ) const { drawing_area drawing(0, range.begin(), UniverseWidth, range.end()-range.begin()); int i_end = range.end(); for( int y = 0, i=range.begin(); i!=i_end; ++i,y++ ) { color_t* c = ColorMap[Material[i]]; drawing.set_pos(1, y); #pragma ivdep for( int j=1; j<UniverseWidth-1; ++j ) { S[i][j] += (V[i][j+1]-V[i][j]); T[i][j] += (V[i+1][j]-V[i][j]); int index = (int)(V[i][j]*(ColorMapSize/2)) + ColorMapSize/2; if( index<0 ) index = 0; if( index>=ColorMapSize ) index = ColorMapSize-1; drawing.put_pixel(c[index]); } } } }; static void ParallelUpdateStress() { tbb::parallel_for( tbb::blocked_range<int>( 1, UniverseHeight-1, GrainSize ), UpdateStressBody() ); }
The parallel version, shown in Example 11-3, requires very little work to create from the serial version. The serial routine needs to be put into a function in a class. Note that structUpdateStressBody {
is equivalent to the class/public we use elsewhere in the book:
class UpdateStressBody { public:
It seems worth letting you see it both ways. The function ParallelUpdateStress
simply uses the parallel_for
algorithm (Chapter 3), and specifies the range, the struct/class UpdateStressBody
we created, and the grain size. The difference between the two versions of the code is minimal. If C++ were to support lambda functions (Chapter 12), the code would look almost identical. Similarly, compare the easy conversion of the serial version in Example 11-4 with the parallel version in Example 11-5.
Example 11-4. Seismic serial: Update velocity
static void SerialUpdateVelocity() { for( int i=1; i<UniverseHeight-1; ++i ) #pragma ivdep for( int j=1; j<UniverseWidth-1; ++j ) { V[i][j] += (S[i][j] - S[i][j-1] + T[i][j] - T[i-1][j])*M[i]; } }
Example 11-5. Seismic parallel: Update velocity
struct UpdateVelocityBody { void operator()( const tbb::blocked_range<int>& range ) const { int i_end = range.end(); for( int i=range.begin(); i!=i_end; ++i ) { #pragma ivdep for( int j=1; j<UniverseWidth-1; ++j ) { V[i][j] += (S[i][j] - S[i][j-1] + T[i][j] - T[i-1][j])*M[i]; } } } }; static void ParallelUpdateVelocity() { tbb::parallel_for( tbb::blocked_range<int>( 1, UniverseHeight-1, GrainSize ), UpdateVelocityBody() ); }
3.16.15.149