Peano
Loading...
Searching...
No Matches
HPC assessment benchmarks

Outcome on the profile mesh

All numbers stem from an eight-core workstation, the profile variant (domain 54, h=1, t=5), eight OpenMP threads, and a Peano build as described above. "Sweeps" is the number of mesh traversals of the time stepping, i.e. the number of time steps (the time step size follows the CFL condition).

Scheme Wall time [s] Sweeps Time step size dt
fv-regular 9.3 35 0.15
fv-amr-1 (radius 1) 174 102 0.05
fd4-rk2 31 64 0.083
fd4-rk3 47 64 0.083
fd4-rk4 62 64 0.083
fd4-rk4-lopsided 82 64 0.083
fd4-rk4-lopsided-ko 82 62 0.083

(fv-amr-1 and the two lop-sided runs use --compiler sequential, see the warning below. The other runs use the default compiler producer-consumer-fusion.)

What we learn from this:

  • None of the schemes blows up or produces NaNs within t=5. The study therefore does not separate stable from unstable schemes yet. This is no evidence for long-time stability: the README asks for t=100 on the full domain, where reflections from the boundary have time to come back. Without checkpoints, we also cannot say anything about the accuracy of the schemes, only about their cost. Run the study with --variant production --end-time 100 (and checkpoints) on a cluster for that.
  • Per time step, the Finite Differences are 2 to 5 times more expensive than the Finite Volumes (fd4-rk2: 0.44 s, fd4-rk4: 0.92 s per sweep, fv-regular: 0.19 s per sweep; all with eight threads). The Runge-Kutta order drives the cost almost linearly (2 stages: 31 s, 3: 47 s, 4: 62 s). The time step size of the Finite Differences is 0.083, i.e. almost half of the Finite Volumes' one, so the Finite Differences need 64 instead of 35 sweeps. Whether an RK order below 4 suffices for a stationary solution is a question of accuracy, which this study cannot answer.
  • The lop-sided advection costs roughly another 30% over plain fd4-rk4 (82 s vs. 62 s, however measured with different compilers). The Kreiss-Oliger dissipation is free in comparison (82 s in both cases).
  • One level of adaptive mesh refinement around the puncture makes the Finite Volumes about 14 times slower (172 s vs. 12.1 s with the sequential compiler). Two effects multiply: three times smaller time steps (102 instead of 35 sweeps) and about 5.5 times more volumes (see the AMR section above), i.e. a factor of 16 in the best case. Local time stepping would help here, as the vast majority of the volumes could use a three times larger time step.
The Finite Differences with lop-sided advection deadlock with the default task graph compiler producer-consumer-fusion if more than one thread is used (all threads of the runs with 2, 4 and 8 threads stood still without a single time step; with one thread the same executable makes progress). The sequential compiler (--compiler sequential) runs fine. See the profiling section below for the diagnosis. The first run of the scheme study consequently hit the timeout for these two schemes.

The runs do not write checkpoints. To compare solution profiles in addition (lapse over radius), pass --extra-arguments "--checkpoint-interval 1" and extract the profiles with probe-single-black-hole.py.

Fourth-order Finite Differences with AMR will be added to this study once the corresponding branch is merged.

Performance study

python3 studies-single-black-hole.py performance --variant profile --threads 1 2 4 8 16 --repetitions 3
-black-hole
-load-balancing

runs the Finite Volumes on the regular mesh and with one AMR level (regular, amr-1; radius 1) for each task graph compiler (sequential, fusion-only, producer-consumer-sequential, producer-consumer-fusion) and every thread count. The thread count is set via OMP_NUM_THREADS and therefore applies to OpenMP builds only. The profile variant uses Peano's stats build. The study measures the wall time of each run (results-performance.csv).

Things to compare:

Outcome on the profile mesh

Wall time of the whole run in seconds (setup, i.e. TwoPunctures and mesh construction, plus 35 time steps on the regular mesh or 102 on the adaptive mesh), eight-core workstation, Finite Volumes, profile variant. The regular mesh figures are the mean of two runs (they differ by less than 0.5 s), the adaptive mesh figures stem from one run.

Mesh Compiler 1 thread 4 threads 8 threads Speedup 1 to 8
regular sequential 39.0 14.5 12.1 3.2
regular fusion-only 43.8 13.4 9.4 4.7
regular producer-consumer-sequential 39.4 14.5 12.4 3.2
regular producer-consumer-fusion 45.3 12.2 9.5 4.8
adaptive (1 level, radius 1) sequential 511 172 3.0
adaptive (1 level, radius 1) fusion-only 584 130 4.5
adaptive (1 level, radius 1) producer-consumer-sequential 504 147 3.4
adaptive (1 level, radius 1) producer-consumer-fusion 592 105 5.6
  • The assumption that the task-based variant is the fastest holds with a qualification. The variant that combines fusion of the sweeps with tasks (producer-consumer-fusion) is the fastest as soon as more than a handful of threads are used: 1.3 times faster than the sequential compiler on the regular mesh and 1.6 times faster on the adaptive mesh with eight threads. It is, however, the slowest variant with one thread (15% slower on both meshes), i.e. it pays an overhead that only parallelism recovers.
  • The task-based variant without fusion (producer-consumer-sequential) is no faster than the sequential compiler on the regular mesh (12.4 s vs. 12.1 s) and only 17% faster on the adaptive mesh. The gain comes from fusing sweeps, not from tasking: fusion-only (no tasks) matches producer-consumer-fusion on the regular mesh (9.4 s vs. 9.5 s). Tasks gain another 24% on the adaptive mesh (105 s vs. 130 s), where the patches are costlier and the tasks have more to overlap.
  • Scalability is poor on all variants. Even the best variants reach a parallel efficiency of only 60% (regular) to 70% (adaptive) on eight threads. On the regular mesh the setup (TwoPunctures plus mesh construction) takes 2.8 s of the 9.5 s, and it does not benefit from the choice of the compiler.
  • The adaptive mesh costs 11 times more than the regular mesh at eight threads with producer-consumer-fusion.

For the compute-bound view of the kernels, see Benchmarks of Compute Kernels of CCZ4.

Profile of the code and recommendations

We profiled the profile variant with Intel VTune (2024.3, hotspot analysis with software sampling, i.e. no hardware counters are needed) on the eight-core workstation. The Peano build is the one described above, with -g -O3. A profile looks like

source /opt/intel/oneapi/vtune/2024.3/vtune-vars.sh
cd studies-performance/regular-producer-consumer-fusion # a directory produced by studies-single-black-hole.py
OMP_NUM_THREADS=8 vtune -collect hotspots -knob sampling-mode=sw -result-dir vtune-regular-producer-consumer-fusion -- ./single-black-hole
vtune -report summary -result-dir vtune-regular-producer-consumer-fusion
vtune -report hotspots -result-dir vtune-regular-producer-consumer-fusion -limit 25

The percentages below refer to CPU time summed over all threads. We profiled five setups:

Setup CPU time [s] Remarks
FV, regular mesh, producer-consumer-fusion, 1 thread 47 cleanest view on the kernels
FV, regular mesh, producer-consumer-fusion, 8 threads 66 spin time 3.1 s, overhead 3.5 s
FV, regular mesh, sequential, 8 threads 86 spin time 6.9 s, overhead 7.5 s
FV, adaptive mesh (1 level), producer-consumer-fusion, 8 threads 837 spin 40 s, overhead 47 s
FD4 (RK4), regular mesh, producer-consumer-fusion, 8 threads 477 spin 35 s, overhead 38 s

Where the time goes

Finite Volumes, one thread.** The three CCZ4 routines plus the generated Rusanov kernel make up 63% of the time: exagrype::ccz4::ncp (24%), the generated kernel CCZ4_timeStepWithRusanov itself (23%, the ncp implementation is partially inlined into it), exagrype::ccz4::source (12%), nonconservativeProduct (3%). TwoPunctures (PunctEvalAtArbitPositionFaster, the initial data) takes 18% of this short run. Heap management takes 8%: free 4.3%, operator new 2.2%, memset 1.2%. memcpy takes 2.4%. Peano's mesh traversal (peano4::grid) is below 2%.

Finite Volumes, eight threads.** The compute kernels shrink to 39% (they scale), whereas everything that moves data does not: memcpy grows from 1.2 s to 5.1 s of CPU time (8%) and peano4::grid takes 3.1 s with producer-consumer-fusion but 7.9 s (9%) with the sequential compiler. That is the effect that makes fusion fast: it cuts the traversal cost by 60%. The remaining 6 s of spinning at barriers (__kmp_fork_barrier) and the serial setup limit the speedup.

Adaptive mesh.** Compute kernels dominate even more (CCZ4_timeStepWithRusanov 17%, source 13%, nonconservativeProduct 4%), but memcpy (8%), TwoPunctures (5%, the new volumes need their initial data), getVertexType (4%) and allocation (free, memset, new, memalign: together 3%) still take a combined 20%. The setup takes 8.7 s of the 105 s.

Fourth-order Finite Differences.** memcpy is the biggest single item with 14% (65 s), followed by ncp (13%), source (10%), peano4::grid::GridTraversalEventGenerator::getVertexType (6%, an event generator which should cost nothing) and the right-hand side kernel itself (4%). Spin and overhead time account for another 15% (73 s).

Task-based deadlock (needs a fix before anything else)

The Finite Differences with lop-sided advection (--lopsided) deadlock with the compilers that spawn tasks as soon as OpenMP uses more than one thread. We attached gdb to a hanging run with eight threads: all eight threads sit inside tarch::multicore::omp::internal::hasTaskFinishedInOpenMP5() (src/tarch/multicore/omp/Tasks.cpp), called from the body of a task created in spawnTaskWithOpenMP5(). That function polls the task's input dependencies in a busy loop inside the task body without ever yielding. If every thread executes a task that waits for a predecessor which still sits in the task queue, nobody is left to run the predecessor. The lop-sided advection adds an action set AddLopsidedAdvection_WaitForTask to the task graph (the plain FD4 scheme has none), which presumably creates the dependent chains that trigger this situation; we did not analyse the dependency structure further. With one thread, or with the sequential compiler, the same code runs through. We consider this a bug of the OpenMP 5 fallback (the compiler offers OpenMP 5, so the OpenMP 6 dependency path is not used).

Recommendations

Sorted by expected benefit per effort; we did not change any code in Peano.

  1. Fix the polling loop of the OpenMP 5 task fallback. Add an #pragma omp taskyield to the loop that polls hasTaskFinishedInOpenMP5() in spawnTaskWithOpenMP5() (the polling loop for the in/out dependency a few lines above it already has one) or defer the task until its predecessors have finished. Until then, run the Finite Differences with lop-sided advection with --compiler sequential. We did not test the taskyield patch.
  2. Use producer-consumer-fusion for runs with at least four threads and sequential for a single thread (see the table above). The default of the script is producer-consumer-fusion, which is the right choice for the production runs but not for debugging on a single core.
  3. Remove the heap temporaries from the generated Finite Volume kernel. The kernel generated from the DSL (kernels/CCZ4_rusanov3d.cpph) allocates 30 temporary arrays per call, each as an array of pointers plus one new double[...] per cell of the batch, and deletes them again. For CCZ4 with 59 unknowns, this costs 8% of the single-thread time (plus the memsets that initialise them). Reusing thread-local scratch buffers, or fusing the loops so that the lambda and flux fields stay in registers, removes that. It lives in RusanovWithExplicitEuler.py and the DSL back end, i.e. in the code generator and not in the benchmark.
  4. Reduce what the kernels compute. ncp (24%) and source (12%) are evaluated three times (once per face direction) on averaged states, and maxEigenvalue is evaluated six times per volume (three times on the halo extent for the Rusanov flux and three times on the result for the next time step). The latter is a cheap routine for CCZ4, but the former two are the most expensive in the profile. Check if their arithmetic vectorises (use the optimisation report as described in Benchmark implementation); we did not run a micro-architecture analysis.
  5. Avoid the data copies. memcpy takes 8% on eight threads for the Finite Volumes and 14% for the Finite Differences, and it is the one item that scales worst with the number of threads (it is probably limited by memory bandwidth). The call stacks show that both for the Finite Volumes and for the Finite Differences, the copies come from toolbox::blockstructured::api::ReconstructPatchWithHalo (its touchCellFirstTime()): in every time step (every Runge-Kutta stage for FD4), each cell copies its patch and the halo of its neighbours into a temporary array before the kernel runs. Reading the halo straight from the face data inside the kernel, or reusing the reconstructed patch between the stages, would save bandwidth. We did not look into how the copy is implemented in detail.
  6. Make the initial data cheaper. TwoPunctures costs 8 to 9 CPU seconds even on the profile mesh and is evaluated for every volume, including every volume that adaptive refinement creates. A single, non-spinning black hole is spherically symmetric, so a table of the metric along the radius (computed once) and an interpolation is enough in principle. This matters for the profile variant and for AMR runs with frequent re-initialisation; it is a one-off cost for the production run. We did not check that the interpolation retains the accuracy that the benchmark requires.
  7. Investigate the size of the refined region and use local time stepping. The refined region is much larger than the radius suggests (see above), and all volumes then use the smallest time step. The adaptive run does 3 times more sweeps for 5.5 times more volumes, i.e. it spends about 16 times the compute of the regular run on a problem where only the area close to the puncture needs the resolution. Since we only profiled one AMR level, we expect the effect to be much worse for the two or three levels that the production setup will need.
  8. Reduce the serial part. The setup (TwoPunctures plus mesh construction, 2.8 s of 9.5 s on the regular mesh) is a fixed cost, as long as it does not scale: it is the main reason for the 60% parallel efficiency. Part of it (TwoPunctures) is item 6.
  9. Look at getVertexType in the Finite Difference profile (6% of 477 CPU seconds for a function that classifies vertices): with eight threads it competes with the kernels; presumably it is called once per vertex and per Runge-Kutta stage. A cached classification would remove it. We did not verify the call count.

The VTune results are not part of the repository. The command lines above reproduce them.

One difficulty in letting ExaGRyPE run is that there's a plethora of different setting that one can pick from. This is quite natural for a research code but quickly becomes challenging for people who just wanna have a go or start benchmarking. It also means that shipping a proper benchmark is far from trivial.

The single black hole benchmark is written in a way such that it supports many different settings. We can therefore play around with them and find good choices. Anecdotal evidence fees into the settings used by benchmarks_exahype2_ccz4_single_black_hole_production_benchmarks. All the different benchmarking ingredients for the HPC assessment are available through the script performance-studies.py.

This page first provides some generic description of the variants. This text serves primarily as background reading. Most readers might skip it. From hereon, we demonstrate how to assess certain system properties systematically:

  1. Determine a proper number of domain subpartitions per rank/node.
  2. Exploit offline load balancing to determine close-to-optimal splittings.
  3. Enable tasking on top of rank-local domain decomposition.
  4. Offload to GPUs.

Each section discussing one of the items above kicks off with a reference configuration. You should be able to run this as it is as kind of a sanity check.

Generic settings

Benchmarking and playing around with the single black hole is never easy, as it is a rather complex setup already with a rather long initialisation and grid construction phase. However, it is possible to benchmark it once you disable plots, the tracers, and you use an unreasonable coarse mesh. That is, we disable a lot of key features for the benchmarking runs. In general, we apply the following simplifications:

  • We reduce the end time drastically to -et 1e-2 via the Python API front-end.
  • We omit any output by defaulting the --plot 0 from the Python call to zero which switches off the plotting.

A typical setup therefore resembles

python3 performance-studies.py -et 1e-2 --plot 0.0 ...
-lift-drop-statistics

Solver, algorithm and optimisation variants

Solvers

The script allows us to toggle between different solver variants from the command line by picking a variant through --solver.

  • Finite Volume solvers all start with a fv- prefix (if you use the command line) followed by the patch size. We exclusively employ enclave solvers in this setup, i.e. each Finite Volume solver spawns tasks.

Load balancing

Proper load balancing between ranks and threads is obviously key for good performance. The benchmark supports a couple of load balancing schemes to empirically study different concepts:

  • generic uses an instance of toolbox::loadbalancing::strategies::cascade::SpreadOutHierarchically. That is, we first populate all ranks and then all threads, unless the Python API specifies the value trees which constraints the maximum number of subpartitions (trees) that we employ per rank.
  • tailored is generic as well, but it uses a cost functional which hard-codes information about the Schwarzschild solution: The closer a cell to the centre, the higher its weight.
  • offline-regular uses the offline balancer over a regular mesh, i.e. it uses the cell size to anticipate what a regular mesh would look like and then computes an optimal partitioning before we even start the compute.

Constraining the number of trees does not mean that threads idle: We use task-based parallelism on top of domain decomposition and hence assume that idle threads grab tasks and hence automatically balance out illpartitioning.

Tasks scheduling and orchestration

For each benchmarks, there are various schedulers:

  • --scheduler native Picks the native, hardcoded scheduler from Peano. This disables all task fusion and also all GPU offloading.
  • --scheduler native-piorities Constructs an instance of benchmarks::exahype2::ccz4::MulticoreOrchestration as task orchestration. It still disables all the task fusion (and therefore GPU offloading), but now activates features such as
  • --scheduler gpu-lazy-evaluation Tasks are shipped off to the GPU lazily, i.e.~when the task results are requested. Compare to the description in tarch::multicore::orchestration::FusionImplementation.
  • --scheduler gpu-timed-execution Tasks are shipped en bloc to the GPU after each mesh sweep that produces tasks. Compare to the description in tarch::multicore::orchestration::FusionImplementation.
  • --scheduler gpu-producer-consumer Shipped off to the GPU with producer-consumer tasksCompare to the description in tarch::multicore::orchestration::FusionImplementation.
  • --scheduler gpu-eager-consumer-tasks Tasks are shipped off to the GPU using Peano's eager consumer pattern.Compare to the description in tarch::multicore::orchestration::FusionImplementation.
  • --scheduler --no-tasks No tasking is used, i.e. everything that could be mapped onto a task is executed immediately.

If an orchestration with task fusion is picked and you employ a combined FV-FD solver, then only the Finite Volume tasks are sent off to the accelerator. Details on this behaviour can be found in benchmarks::exahype2::ccz4::MulticoreOrchestration.

Studying the right number of subpartitions per node

We start with some experiments where we artifically restrict the number subpartitions uses on the node. If we use one subpartition per core, we quickly run into a situation where the individual subpartitions become too small and overhead eats up all scalability improvements. Therefore, a first step restricts the number of trees through --trees. While we do so, we first disable all tasking manually, and we also stick exclusively to Finite Volumes:

python3 performance-studies.py -s fv-6 --plot 0.0 --scheduler no-tasks --trees ... ...



Example setup
python3 performance-studies.py -s fv-6 -et 1e-2 --plot 0.0 --scheduler no-tasks --trees 32 --cell-size 1.8 --output test
This setup yields a relatively small regular grid scenario which should complete within a few minutes and be well-balanced without any further effort.

Building all the benchmarks in one go

rm README-peano-*
export PYTHONPATH=../../../../python:../../../../applications/exahype2/ccz4
module load python
for TREES in 1 2 3 4 6 8 9 16 27 32 64 128 256
do
python3 performance-studies.py -s fv-6 -et 1e-2 --plot 0.0 --cell-size 0.6 --scheduler no-tasks --trees $TREES --output peano-regular-$TREES-trees
python3 performance-studies.py -s fv-6 -et 1e-6 -amr --plot 0.0 --cell-size 0.00123 --scheduler no-tasks --trees $TREES --output peano-adaptive-$TREES-trees
done
Definition ccz4.py:1
This file is part of the ExaHyPE 2 project within Peano 4.
Definition __init__.py:1

There are two job script in the scripts subdirectory, which can serve as a starting point if you want to submit experiments to a supercomputer with an array job. This script is for Hamilton 8 but most Slurm scripts will look similar.

Once the data is in, it might be best to package it into two archives

tar -czvf results-adaptive.tar.gz results-adaptive-*
tar -czvf results-regular.tar.gz results-regular-*

and to copy them over onto a local computer. There is a dedicated script plot-trees-vs-runtime.py in the scripts subdirectory of the benchmark that can read zips and create output data. The script is based upon the load balancing postprocessing scripts and yields plots similar to the one below.

export PYTHONPATH=~/git/Peano/src/toolbox/loadbalancing/api/postprocessing
python3 scripts/plot-trees-vs-runtime.py results-regular.tar.gz --output trees-vs-runtime-regular
python3 scripts/plot-trees-vs-runtime.py results-adaptive.tar.gz --output trees-vs-runtime-adaptive
This file is part of the load balancing project within Peano 4.
Definition __init__.py:1

Regular grid outcomes

The above images stem from an OpenMP run on Hamilton 8 (top) and a run on the same machine using Intel's TBB with Peano's TBB extension (bottom).

We see that there is a sweet spot whenever we hit the canonical \( 3^d \) subpartition count. We also see that there is limited sense in going beyond 27 subpartitions on a rank. The crucial question here is whether this is a general pattern, or if the workload per partition then simply becomes too small.

To a certain degree, we have to analyse adaptive meshes to find an answer to this question, although an in-depth performance analysis would obviously be even better.

Adaptive mesh outcomes

The above images stem from an OpenMP run on Hamilton 8 (top) and a run on the same machine using Intel's TBB with Peano's TBB extension (bottom).

The adaptive mesh data is more interesting: The balancing of cells with the present load balancing scheme is poor. Consequently, we need a more sophisticated load balancing scheme.

Set up an adaptive mesh scenario with offline load balancing



This part is currently under construction and not ready to be released yet.

Find the right tasking back-end

Once we are reasonably satisfied with the load balancing per rank, it is an interesting question what kind of tasking back-end is most advantageous for the settings. That is, now we fix the number of subpartitions to a reasonable choice and we alter the scheduling orchestration. We know that we cannot exploit all cores through domain decomposition alone, and hence hope that the tasks step in and help us exploit those cores that we haven't used yet, i.e. we use the whole node, use a reasonable number of subtrees per node and hence use a few cores to handle those subtrees, and then let those subtrees spawn tasks which should bring in the idling remaining threads.



Example setup
python3 performance-studies.py -s fv-6 -et 1e-2 --plot 0.0 --cell-size 1.8 --trees 32 --scheduler native --output test
This setup yields a relatively small regular grid scenario which should complete within a few minutes and be well-balanced without any further effort. As we pick native as scheduler, the test should use more than the 32 threads that we occupy with subpartitions.

Building all the benchmarks in one go

rm README-peano-*
export PYTHONPATH=../../../../python:../../../../applications/exahype2/ccz4
module load python
for SCHEDULER in no-tasks native gpu-lazy-evaluation gpu-timed-execution gpu-eager-consumer-tasks gpu-producer-consumer
do
python3 performance-studies.py -s fv-6 -et 1e-2 --plot 0.0 --cell-size 1.8 --trees 27 --scheduler $SCHEDULER --output peano-regular-$SCHEDULER
python3 performance-studies.py -s fv-6 -et 1e-6 -amr --plot 0.0 --cell-size 0.00123 --trees 64 --scheduler $SCHEDULER --output peano-adaptive-$SCHEDULER
done

Despite the prefix gpu, all of these schemes also should work on a CPU-only system. Again, there's an example Slurm submission script in the scripts subdirectory which works for Hamilton. This time, we cannot straightforwardly use array jobs, so I decided to stick with one big script which runs through the variants. We do not farm out.

The mesh size is delibertely chosen coarser than in a previous setup. As we bundle tasks, we temporarily need more memory, so having some slack would help.

Postprocessing

Again, the cleanest way is to create on archive as my postprocessing script can handle archives directly:

tar -czvf results-scheduler.tar.gz results-scheduler*.out
[... maybe copy stuff onto your local machine]
export PYTHONPATH=~/git/Peano/src/toolbox/loadbalancing/api/postprocessing
python3 scripts/plot-schedulers.py --output different-schedulers results-scheduler.tar.gz

This time, I pack the regular grid data and the adaptive grid data into one single archive. The script should visualise both variants, and it also provides a plot with a linear y-axis and one where we use the log scale.

Scale OpenMP TBB
linear
log

The above images stem from an OpenMP run on Hamilton 8 (first two plots) and a run on the same machine using Intel's TBB with Peano's TBB extension (bottom two plots).

Evaluation

The lessons learned now are not trivial anymore:

  • Native tasking on top of the domain decomposition pays off.
  • Lazy evaluation where these tasks are parked and then processed en bloc once we need them is not a good idea. It likely introduces load imbalances, as we discuss below.
  • Timed execution means that we wait for all tasks to build up and then handle them en bloc at the end of the mesh traversal. This might be a good idea for GPUs which need a reasonable chunk of work to perform well, but it is a very poor idea on a CPU-only system.
  • Eager consumers seem to work fine for adaptive meshes with unreasonable load balancing, but introduce too much overhead for a regular mesh, such that the tasked variant with this orchestration is actually slower than the baseline code without any tasking.



Todos:
  • Add a remark to our paper that the memory footprint can become a problem if we park tasks. We also have to quantify this impact.
  • Run some performance analysis what happens in the bad cases.

GPU offloading



This part is currently under construction and not ready to be released yet.

Scaling



This part is currently under construction and not ready to be released yet. Please ignore all the stuff that is written below.

Script to create output plots

Below is a template of a script how to creates data points. You will have to amend the prefix accordingly. Note that we really only are interested in one line from the output and that's the time to solution that is spent on the actual solver, i.e. bare any I/O and mesh generation.

export PREFIX=tbb or omp or sycl
export CELL_SIZE=1.8
OUTPUT=results/$PREFIX.txt
echo $PREFIX > $OUTPUT
for AMR in without-amr with-amr
do
for TREES in 1 6 8 12
do
for SCHEDULER in native-no-priorities native tailored parallel-for subtasks subtasks-and-kernel-parallelisation
do
FILE=results/$PREFIX-$AMR-$SCHEDULER-$TREES-trees-$CELL_SIZE.txt
echo $FILE >> $OUTPUT
grep "time stepping:" $FILE >> $OUTPUT
done
done
done
echo wrote all data to $OUTPUT
tar -cf $PREFIX-$SCHEDULER-without-amr-$TREES-trees.tar --files-from /dev/null
The parallel namespace is Peano's core abstracts from both MPI and multicore parallelisation.
I've written an API to IIT, but I'm not currently using.

The last line packs the data from one upscaling exercise into a tar archive. Single files are obviously more convenient to move around between computers, but archives are notably directly supported by Peano's postprocessing scripts.

Plotting performance data

Get the tar archives. After that, we can use the out-of-the-box Peano scripts to generate the plots

export PREFIX=omp
export TREES=8
OUTPUT=$PREFIX-$TREES-trees-without-amr
python3 ../../../../python/exahype2/postprocessing/plot-scaling.py --max-cores-per-rank=-1 --log-x --log-y --output $OUTPUT \
--label "native tasking, subtasks, subtasks and parallel kernels" --title "No AMR" \
$PREFIX-native-without-amr-$TREES-trees.tar.gz \
$PREFIX-subtasks-without-amr-$TREES-trees.tar.gz \
$PREFIX-subtasks-and-kernel-parallelisation-without-amr-$TREES-trees.tar.gz
export OUTPUT=$PREFIX-$TREES-trees-with-amr
python3 ../../../../python/exahype2/postprocessing/plot-scaling.py --max-cores-per-rank=-1 --log-x --log-y --output $OUTPUT \
--label "native tasking, subtasks, subtasks and parallel kernels" --title "AMR, patch size 3, cell size 1.8" \
$PREFIX-native-without-amr-$TREES-trees.tar.gz \
$PREFIX-subtasks-without-amr-$TREES-trees.tar.gz \
$PREFIX-subtasks-and-kernel-parallelisation-without-amr-$TREES-trees.tar.gz

which clearly showcase that the kernel parallelisation pays off even though the overall scalability is poor with the present rather coarse mesh:

Some other setups

Here are some canonical settings:

Description Solver Cell size End time Patch size Remarks Build command
Minimal setup Finite Volumes 5.4 1.0 6 Toy problem with 729 cells. Tells you if the code, in principle, works. Should crash at one point suddenly as the eigenvalue becomes zero, which showcases that this setup is too coarse to capture anything that has a physical meaning. python3 performance-studies.py -s fv-6 -et 1.0 --plot 0.0 --cell-size 5.4
Simplest FV setup for single node workstation (laptop) Finite Volumes 1.8 1e-1 6 You cannot see AMR here, but already gives you 19,683 cells. Load balancing should be really good. This is kind of the largest regular mesh setup that I can fit on a local laptop. python3 performance-studies.py -s fv-6 -et 0.1 --plot 0.0 --cell-size 1.8