Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 59 additions & 7 deletions src/init_grid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,54 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void)
m_dStartIterConsSandAllCells = 0;
m_dStartIterConsCoarseAllCells = 0;

// And go through all cells in the RasterGrid array
// Use OpenMP parallel loop with reduction clauses for thread-safe accumulation
// Cache-aligned accumulator structure to prevent false sharing between threads
// Each structure is padded to 64 bytes (typical cache line size) to ensure
// different threads' accumulators reside in separate cache lines
struct alignas(64) ThreadLocalAccumulators
{
int nZeroThickness;
double dConsFine;
double dConsSand;
double dConsCoarse;
double dSuspFine;
double dUnconsFine;
double dUnconsSand;
double dUnconsCoarse;

ThreadLocalAccumulators() :
nZeroThickness(0), dConsFine(0), dConsSand(0), dConsCoarse(0),
dSuspFine(0), dUnconsFine(0), dUnconsSand(0), dUnconsCoarse(0) {}
};

// Determine maximum number of threads for pre-allocation
// This scales efficiently from dev machine (8 cores) to production (32+ cores)
#ifdef _OPENMP
#pragma omp parallel for collapse(2) reduction(+ : nZeroThickness) \
reduction(+ : m_dStartIterConsFineAllCells, m_dStartIterConsSandAllCells, m_dStartIterConsCoarseAllCells) \
reduction(+ : m_dStartIterSuspFineAllCells, m_dStartIterUnconsFineAllCells, m_dStartIterUnconsSandAllCells, m_dStartIterUnconsCoarseAllCells)
int const nMaxThreads = omp_get_max_threads();
#else
int const nMaxThreads = 1;
#endif

// Pre-allocate cache-aligned accumulators for each thread
// This eliminates false sharing and allows lock-free parallel accumulation
std::vector<ThreadLocalAccumulators> threadAccum(nMaxThreads);

// Parallel loop without reduction clauses - each thread accumulates into its own cache line
// This approach eliminates the OpenMP reduction overhead that caused thread synchronization bottlenecks
#ifdef _OPENMP
#pragma omp parallel for collapse(2) schedule(static)
#endif
for (int nX = 0; nX < m_nXGridSize; nX++)
{
for (int nY = 0; nY < m_nYGridSize; nY++)
{
// Get thread ID to access this thread's private accumulator
#ifdef _OPENMP
int const tid = omp_get_thread_num();
#else
int const tid = 0;
#endif
ThreadLocalAccumulators& acc = threadAccum[tid];

// Re-initialise values for this cell
m_pRasterGrid->m_Cell[nX][nY].InitCell();

Expand All @@ -132,7 +168,7 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void)

if (dSedThickness <= 0)
{
nZeroThickness++;
acc.nZeroThickness++;

// Note: Logging from parallel regions can cause race conditions, but this is for debugging only
// In production, consider collecting problematic cells and logging after the parallel region
Expand All @@ -149,6 +185,7 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void)
m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50();
}

// Accumulate into thread-local variables (no synchronization required!)
// Note that these totals include sediment which is both within and outside the polygons (because we have not yet defined polygons for this iteration, duh!)
m_dStartIterConsFineAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetConsFineDepthAllLayers();
m_dStartIterConsSandAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetConsSandDepthAllLayers();
Expand All @@ -169,6 +206,21 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void)
}
}

// After parallel region: combine thread-local results sequentially
// This is fast (O(nThreads), typically 8-32 operations) compared to the parallel work (O(nCells))
// and eliminates all the false sharing and synchronization overhead from the parallel loop
for (int t = 0; t < nMaxThreads; t++)
{
nZeroThickness += threadAccum[t].nZeroThickness;
m_dStartIterConsFineAllCells += threadAccum[t].dConsFine;
m_dStartIterConsSandAllCells += threadAccum[t].dConsSand;
m_dStartIterConsCoarseAllCells += threadAccum[t].dConsCoarse;
m_dStartIterSuspFineAllCells += threadAccum[t].dSuspFine;
m_dStartIterUnconsFineAllCells += threadAccum[t].dUnconsFine;
m_dStartIterUnconsSandAllCells += threadAccum[t].dUnconsSand;
m_dStartIterUnconsCoarseAllCells+= threadAccum[t].dUnconsCoarse;
}

if (m_bHaveWaveStationData && (! m_bSingleDeepWaterWaveValues))
{
// Each cell's value for deep water wave height and deep water wave orientation is interpolated from multiple user-supplied values
Expand Down Expand Up @@ -210,7 +262,7 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void)
// GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions);
// pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str());
// pDataSet->SetGeoTransform(m_dGeoTransform);
// double* pdRaster = new double[m_ulNumCells];
// double* pdRaster = new double[m_ulNum_Cells];
// int nn = 0;
// for (int nY = 0; nY < m_nYGridSize; nY++)
// {
Expand Down