|
TRUST 1.9.8
HPC thermohydraulic platform
|
A key feature for code optimisation is performance measurement. TRUST offers different solutions, using TRUST internal features or external libraries.
In order to obtain statistics on the performance of a test case, TRUST uses counters. Counters are C++ objects.
Their main purpose is to serve as time watches for parts of the code you deem important. They also track additional metrics:
Timing statistics:
Usage metrics:
Identification:
Level management: Counters also have a certain level. The level of a counter is represented by an integer. It makes sure that you know when you open your counter. Indeed, if a counter of level 1 is running, you can only start a counter of level 2. Otherwise, the code will stop. In the same logic, you can only close the most recently opened counter. Because of this interlock structure, counters also have another interesting metric called alone_time. It is the elapsed time where the considered counter is the last opened one (i.e., excluding time spent in nested counters). This metric is printed in the CSV output file and helps identify the intrinsic cost of each code section.
Example of the level hierarchy:
This ensures proper nesting and helps track where time is spent in your code hierarchy.
Those counter objects are managed by the Perf_counters class. In practice, you will only need to interact with the Perf_counters class (not with individual counter objects directly). The Perf_counters class follows a singleton pattern and a Pimpl pattern, such that the implementation of the class is hidden in the Perf_counters.cpp file. The unique instance of Perf_counters can be called inside the code by using:
The unique instance of Perf_counters will be created at the first statistics() call.
The counters managed by the Perf_counters instance are separated in two types:
The basic API for counters in TRUST is as follows:
MY_COUNTER_KEY is either a STD_COUNTERS if you want to open a standard TRUST counter or the std::string that corresponds to the description of the custom counter you try to open. In the statistics().begin_count() function, the level parameter is optional. If omitted, the counter will automatically use the one defined at the creation of the counter. If you are having trouble determining the level of your counter, you can use the function statistics().get_last_opened_counter_level() to know the level of the last opened counter. The count parameter specifies how much to increment the counter's total count (i.e., how many times to record this begin/end cycle). It is set by default to 1. The quantity parameter specifies how much to increment the quantity attribute of your counter and is by default set to 0.
During a TRUST computation, TRUST statistics are measured through 3 main steps:
At the end of each step, the counters are reset and statistics are printed in the two files:
The first file contains aggregated stats that are the most commonly used alongside some information regarding the environment of your computation (date, OS, CPU model, GPU model if you run a GPU computation, number of CPU processors used, ...). It has been designed to be human readable, but it is not easy to parse it informatically. The second file has been created for easily parsing each and every counter's data with your favorite csv parsing tool, for example pandas.
The first time steps can take more time thant the rest, if you want to discard them of your stats, add the following line of code right before the time loop:
Here is the list of the standard TRUST counters:
| Key | Description | Family | Is_communication | Is_gpu |
|---|---|---|---|---|
| total_execution_time | Total time | None | False | False |
| computation_start_up | Computation start-up | None | False | False |
| timeloop | Time loop | None | False | False |
| backup_file | Back-up operations | None | False | False |
| system_solver | Linear solver resolutions Ax=B | None | False | False |
| petsc_solver | Petsc solver | None | False | False |
| implicit_diffusion | Solver for implicit diffusion | None | False | False |
| compute_dt | Computation of the time step dt | None | False | False |
| turbulent_viscosity | Turbulence model::update | None | False | False |
| convection | Convection operator | None | False | False |
| diffusion | Diffusion operator | None | False | False |
| gradient | Gradient operator | None | False | False |
| divergence | Divergence operator | None | False | False |
| rhs | Source terms | None | False | False |
| postreatment | Post-treatment operations | None | False | False |
| restart | Read file for restart | None | False | False |
| matrix_assembly | Nb matrix assembly for implicit scheme: | None | False | False |
| update_variables | Update ::mettre_a_jour | None | False | False |
| mpi_sendrecv | MPI_send_recv | MPI_sendrecv | true | False |
| mpi_send | MPI_send | MPI_sendrecv | true | False |
| mpi_recv | MPI_recv | MPI_sendrecv | true | False |
| mpi_bcast | MPI_broadcast | MPI_sendrecv | true | False |
| mpi_alltoall | MPI_alltoall | MPI_sendrecv | true | False |
| mpi_allgather | MPI_allgather | MPI_sendrecv | true | False |
| mpi_gather | MPI_gather | MPI_sendrecv | true | False |
| mpi_partialsum | MPI_partialsum | MPI_allreduce | true | False |
| mpi_sumdouble | MPI_sumdouble | MPI_allreduce | true | False |
| mpi_mindouble | MPI_mindouble | MPI_allreduce | true | False |
| mpi_maxdouble | MPI_maxdouble | MPI_allreduce | true | False |
| mpi_sumfloat | MPI_sumfloat | MPI_allreduce | true | False |
| mpi_minfloat | MPI_minfloat | MPI_allreduce | true | False |
| mpi_maxfloat | MPI_maxfloat | MPI_allreduce | true | False |
| mpi_sumint | MPI_sumint | MPI_allreduce | true | False |
| mpi_minint | MPI_minint | MPI_allreduce | true | False |
| mpi_maxint | MPI_maxint | MPI_allreduce | true | False |
| mpi_barrier | MPI_barrier | MPI_allreduce | true | False |
| gpu_library | GPU_library | GPU_library | false | true |
| gpu_kernel | GPU_kernel | GPU_kernel | false | true |
| gpu_copytodevice | GPU_copyToDevice | GPU_copy | false | true |
| gpu_copyfromdevice | GPU_copyFromDevice | GPU_copy | false | true |
| gpu_malloc_free | GPU_allocations | GPU_alloc | false | true |
| interprete_scatter | Scatter_interprete | None | true | false |
| virtual_swap | DoubleVect/IntVect::virtual_swap | None | true | False |
| read_scatter | Scatter::read_domaine | None | true | False |
| parallel_meshing | Parallel meshing | None | False | False |
| IO_EcrireFicPartageBin | write | IO | False | False |
| IO_EcrireFicPartageMPIIO | MPI_File_write_all | IO | False | False |
You can use them whenever you need to.
As explained above, on top of standard counters, you can also create and use custom counters. To create a new custom counter, you just need to add the following in your code:
Then, you can open and close your new counter, using std::string counter_description as your new counter key. All of the custom counters will be printed in both TU files.
Some external profilers can be directly used on a TRUST data file. To find the appropriate option to use them, run:
Below, we present the three most useful ones.
First, install the Perf library if you don't have it yet.
Then, you just need to:
Perf is dedicated to CPU profiling. It will enable you to locate the main performance bottlenecks inside your code.
First, make sure the Heaptrack package is installed on your computer.
Then, you just need to:
Heaptrack is dedicated to monitor memory usage (allocations: their number and size). It will enable you to find excessive allocations and possibly memory leaks.
Normally, Nsight system is already available in TRUST. You can also install it alone or alongside the Cuda Toolkit.
Then, you just need to:
Nsight system is dedicated to GPU profiling. It will enable you to locate the main performance bottlenecks inside your code. It detects the code not yet ported on GPU, and helps you visualize the data copy between host and device memory. Nsight system is also useful with CPU-only runs thanks to the rich labeling of the code.