If you are reading this, it is probable that you are going to contribute to SMASH. First of all, we welcome your interest and thank you for your work. Hopefully, you find what you need in the following. Otherwise feel free to contact the development team by reporting issues or contact us by email.
Note that any contributions must be licensed under the same terms as SMASH, see our LICENSE. In particular, it has been decided that changes to C++ or CMake code should be reflected in the years of the copyright notice. Therefore, be sure that the present year is added there in files that you are going to commit.
As an external contributor, go to https://github.com/smash-transport/smash, fork the repository, work on a topic branch and then create a pull request. Details on this workflow can be found e.g. here.
The first and main guideline beyond all the following ones in this file is: Be consistent with anything that is already existing in the codebase. This is valid in the most general sense, ranging from style, to notation, to nomenclature and whatever else you might notice.
To run the various unit tests, use the following:
make test
Another way to do this is to use the CMake test runner:
ctest
This has the advantage that it can also be used for running tests in parallel on a multicore machine, e.g. via
ctest -j4
(on a quad-core machine).
If a test crashes, there might be some leftover in the test_output
folder, causing the test to always fail when run again. To fix this problem, just remove the folder.
The SMASH binary memory usage can be checked for the different modi with the the following cmake targets:
make memcheck_collider make memcheck_box make memcheck_sphere
Alternatively, the binary can be checked manually via:
valgrind -v ./smash
Note: There is known bug with valgrind-3.11
that leads to an error about an unrecognized instruction. The memchecks will not run with this version.
There are different build types available, which compile the SMASH code for different situations.
To build a binary with debug symbols (which is useful to get stacktraces in case of errors), with support for TRACE
and DEBUG
logging output (which costs a lot of performance) and with internal consistency checks (which can be useful for finding and fixing bugs, but are expensive), use the Debug
build type:
cmake .. -DCMAKE_BUILD_TYPE=Debug
For a build with optimizations, without debug symbols, without support for TRACE
or DEBUG
logging and without internal consistency checks, use the Release
build type:
cmake .. -DCMAKE_BUILD_TYPE=Release
For a profiling build, use the Profiling
build type:
cmake .. -DCMAKE_BUILD_TYPE=Profiling
The default build type is 'RelWithDebInfo', which is the same as the Release
build type, except that it enables debug info for better stacktraces when SMASH crashes. The debug info makes the binaries larger, but only has a marginal performance impact.
To find cmake build errors (best debugged with full compiler output) use:
VERBOSE=1 make
If a call to a C function without explicit namespace qualification is made, a warning appears (if enabled) during compilation, if some code tries to use a C library function call. The only correct way to write C++ code is to use the C++ interface by specfing the correct std namespace.
This option is OFF by default and is checked from time to time. Enable it as follows
cmake .. -DDEPRECATE_C_FNS=ON make
Note: Do not use this as a default, since it increases compilation time. Use clang for compilation here, since gcc runs into an internal compiler error, warnings are however still reported.
The following tools can be helpful for development:
NOTE: The formatting with the above mentioned clang-format
version is enforced at each merge to the main
branch.
clang-format is a part of the clang compiler. The usage of version 13.0.x is enforced (x stands for any number). You can download the binaries here.
Make sure to pick a pre-built binary for your system. For example, for Ubuntu you could run:
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04.2 LTS Release: 16.04 Codename: xenial
This tells you to download "Clang for x86_64 Ubuntu 16.04". (You might have to look for an older version to get pre-built binaries.)
It is sufficient to unpack the archive with tar xf
and to copy only the binary you need (clang-format
in the bin
folder of the archive), see "Installing binaries as a user" below.
NOTE: The name of the clang-format executable should not have any suffix, like e.g. clang-format-13
. If this is the case, a possible workaround is to create a symbolic link to this exectuable called clang-format
in a directory included in the environment variable PATH. For example:
Of course, if the user has writing permissions, the symbolic link can be also directly created in the same directory in which the executable is and, if this is already in the system PATH (for example /usr/bin
), no further actions are needed to use it as a normal command.
We use cpplint to enforce some of our style guide lines as part of our tests. You can install it like this:
pip install --user cpplint
You might have to add ~/.local/bin
to your $PATH
, see "Installing binaries
as a user".
You can use cppcheck to find some problems in the code, but beware it has quite a few false positives. Download and compile the latest version:
git clone git://github.com/danmar/cppcheck.git cd cppcheck make
You can then copy it to your local binary folder, see "Installing binaries as a user".
If you want to check the spelling in comments, try codespell. You can install it like this:
pip install --user codespell
It is the same as installing cpplint.
If you do not have administrator privileges on the machine you are using, you can still install software locally. Just copy the binary (in this example clang-format
) to a local folder and update your path, e.g.:
mkdir ~/bin cp ./clang-format ~/bin echo 'export PATH=${PATH}:${HOME}/bin' >> ~/.bashrc source ~/.bashrc
After this, you can just copy executable files to ~/bin
to install them. This also works for other executables like cpplint. You might have to set them to be executable with chmod u+x ~/bin/my-binary
.
We use doxygen for generating documentation from our code. The online version of the code documentation is found here.
You need to have Doxygen installed. Then, from your build directory, just call:
make doc
and you'll find doc/html/index.html
in your build directory. Open e.g. with
firefox doc/html/index.html
Additionally, there are two more targets that can be used to test the completeness of the documentation:
make undocumented make undocumented_count
The first target outputs all Doxygen warnings about missing documentation and the second one only counts (and outputs) the number of warnings. Both are building the Doxygen documentation only for completely documented entities, but the main purpose of both is that all warnings are displayed when running.
In the build directory, run
make user
to obtain the files in doc/user/. Open doc/user/index.html in your favorite browser.
New pages can be created using the \page
Doxygen command, which takes an anchor name as first argument and the page title as second argument. It is possible to use the \page
command with the same anchor name in order to add content to the same page from different places. Therefore, the second argument is optional and should be used only at the first \page
occurrence (to avoid inconsistencies). In general, this is not trivial, but in SMASH we collect pages "declarations" in the doc/index.dox file, which is meant to enforce pages ordering (Doxygen picks up pages in the order it encounters them). So, in this file titles should be specified, while elsewhere not.
To make searching for documentation pages easier, a doxypage_
prefix for their anchors has been decided to be used and you should do the same. For instance, if you create a new documentation page, you will declare it with title in the doc/index.dox file where you want it to appear as
\page doxypage_my_new_page My wonderful title
and then add content to it simply by using
/** * \page doxypage_my_new_page * * [...] */
at the desired place in the codebase.
Sub-pages are pages themselves, but Doxygen classifies pages as sub-pages as soon as it encounters the \subpage
command in a given \page
. At that point the anchor given to the \subpage
command tells Doxygen which page should be marked as sub-page of which other. Also sub-pages ordering is fixed in the doc/index.dox file and you may refer to it for further information. In short, remember to use the \subpage
command **only* in the index file and the \ref
command elsewhere.
Code documentation has two important purposes:
Doxygen is very flexible in the comments it accepts for documentation generation. In general, it uses both comments and the source code itself to generate the documentation. In general documentation should look like this:
Doxygen has a lot of commands for markup. Most importantly it uses special comment formatting. Comments meant for the documentation are either prefixed with ///
or wrapped with /** ... */
. For all the connection to code and some special layout commands, refer to the Special Commands in Doxygen.
The code documentation follows a few rules concerning the formatting and the question what should be documented:
\param[in]
for input parameters; \param[out]
for input parameters that get modified within the function; \return
for what is returned; \throw
for exception that are thrown\tparam
\f$ <math expression> \f$
\f[ <math expression> \f]
\f{<env-name>} <math expression> \f}
\mathbf{x}
instead of \vec{x}
for vectors in formulae and use \boldsymbol{\alpha}
for greek letters or symbols like \nabla
[...]
make doc
(see below)In order to refer to a paper inside a doxygen comment, the \iref
command should be used:
Here, XXX
should be the BibTex key for the paper from Inspire e.g. Weil:2016zrk
. In order to find it, search for the paper on http://inspirehep.net and then click on 'BibTex', which will show the complete BibTex entry (you only need the key, which is in the first line). Doxygen will automatically translate <a href= https://inspirehep.net/search?p=XXX&of=hd>XXX</a> \cite XXX
into a link to the paper on Inspire.
After adding a new reference, you should run the script doc/get_bibtex.sh
, which will update the file /doc/inspire.bib
by fetching the BibTex entries of all \iref
references from Inspire. It also reports references that are not found on Inspire.
References that are not contained in the Inspire database can be handled as follows: A corresponding BibTex entry should be put into doc/non_inspire.bib
manually. It can then be referenced via the \cite
command:
The User Guide will be written in the code base, i.e., documentation of configuration options are described where they are used. Comments that are in normal doxygen format do not appear in the User Guide. Instead only multi-line comments of the form /*! ... */
will be used. Example:
The workflow is that doc/CMakeLists.txt
extracts all /*! ... */
Sections into doc/userguide.dox
which is then included into the User Guide Doxygen tree. Currently, Cmake processes all files specified in the doc/UserInputFiles.cmake
in that given order.
The markdown documents included in the repository follow the Markdown dialect of Github that is specified here.
Major code changes are tracked in the CHANGELOG.md file. This file is meant to inform the users, but also the other developers. The structure of the sections is explained at the beginning of the file itself. Every version has its own section with the date it was tagged. Starting from the bottom with the section for the first public release. On top of these sections an Unreleased section is kept. The file is meant to be kept up-to-date together with the code changes itself, so that at all times the code status is reflected. Most important are (breaking) changes to our in- or output file. Here, all details have to be specified. For all other code updates only major changes like new features or bug fixes have to be given.
Our Naming & Formatting Rules follow the Google Styleguide. We keep a copy of the relevant sections to be able to make small adjustments and hide the C++ Coding Guidelines sections.
Note that we use clang-format
for formatting (installation explained above, usage explained below), so that you do not have to worry about formatting by hand to be in accordance with the style guide.
A notable naming rule is that class member variables have to end in an underscore (_
).
incoming_particles
outgoing_particles
ParticleData
objects (type ParticleList
) we useparticle_list
PdgCode
objects we usepdg_list
particle_a, particle_b, particle_c, ...
Don't put bla and foo on the same line to better show what the code is doing.
If a new key is added to the config.yaml
, it should be done in the following way:
enum class
to forwarddeclarations.h for the new key type.Configuration::Value
cast operator overload.InputKeys
class. Doing so you will also add the needed description for the User Guide.All C++ code has to be formatted by running clang-format
, (version 13.0.0
) while CMake code requires cmake-format
(version 0.6.13
) to be run. These two programs automatically format the code in SMASH correctly. Use the helper script in SMASH's bin directory to format the source code via
./codebase-format-helper.bash C++ -p ./codebase-format-helper.bash CMake -p
or by simply using
./codebase-format-helper.bash -p
to format all languages at once. Review and commit changes afterwards. You can also use the -t
option to test whether the code is correctly formatted (the script has also a -h
option that you can check out). clang-format
does changes that don't look good, you can disable it locally using comments like this:
// clang-format off ... // clang-format on
The SMASH source coding style can be checked via:
make cpplint 2>&1 | grep -v 'Done processing'
cpplint
checks the formatting of the code and is also part of the unit tests.
cppcheck
is a static code analyzer that can be run additionally, but yields a lot of false positives:
make cppcheck
The make
targets will be created by CMake stage only if given versions of the commands are installed and found, namely version 1.6.0 for cpplint
and version 2.8 for cppcheck
.
All floating point numbers are represented using doubles.
As a guideline, try to include only those header files in the class which are directly being used by the file.
In general, input and output interfaces should be backwards compatible, when introducing changes. If there are backwards incompatible changes that affect the YAML input or the binary output, these should be mentioned in the CHANGELOG file. For example, input keys that got deprecated or removed should be listed.
The release notes need to include a prominent mention of all changes, backwards incompatible or not. In particular, newly introduced config parameters have to be mentioned.
In general, the usage of third party codes is discouraged. If there is a scientific necessity or a major performance gain or time saving by using third party libraries, they can be linked to SMASH. For the common ones, a description on how to install them in the README file is sufficient whereas for the less common ones, including them in the **_3rdparty_** folder and shipping them with SMASH is the better solution. Of course, this involves ensuring the proper copyright.
Some third-party libraries are already shipped within SMASH. More information about them and, in particular, instruction about how to update them can be found in the README file in the **_3rdparty_** folder.
This section discusses tools that can be used to measure the performance.
A basic benchmark script for linux machines is included in the bin
directory. It runs different common setups of SMASH and measures them with perf
(see below). Usage instructions can be found in the corresponding README.
In the codebase some nanobenchmarking code has been written in order to let the developer inspect critical parts of the code, e.g. counting CPU cycles. This is in general unused and it is by default excluded from compilation. In order to get access to it and be able to use it, you need to toggle the correspondent CMake option passing -DENABLE_NANOBENCHMARKING=ON
command line option to cmake
. Please note that nanobenchmarking is not supported on ARM architectures like Apple machines with M1 chips.
You can tell cmake to create a build for profiling with the Profiling
build type:
cmake -DCMAKE_BUILD_TYPE=Profiling ..
This will compile the smash code like Release
mode, but with the -pg
flag to instrument the code and create a gmon.out
file whenever you run a binary. You can look at the gmon.out
information with gprof
.
# first run smash to create the gmon.out file ./smash # now run gprof to see the profile information gprof smash|less
A different method for profiling SMASH uses performance counters and the perf
tool on Linux. perf
uses a feature of the CPU (and OS support) to count performance relevant events in the normal execution of the program. Therefore, SMASH should execute as "normal as possible", but with meta information about the program to improve the reporting capabilities of perf
. To get useful results compile SMASH in RelWithDebInfo
mode, i.e. via
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
Now you can use perf
to collect information for subsequent investigation (similar to gprof
) or have it collect global stats directly and output them after execution. Here are some of the common uses:
# Collect default information about the execution (cycles, IPC, stalls, # branching, ...) perf stat -B ./smash # Collect detailed information about a run (collecting 'cycles') perf record --call-graph dwarf ./smash perf report --stdio
perf
also supports to collect different information, but this can be very CPU specific and also hard to interpret. Therefore, I recommend to focus only on cycles
(for record
) and the default stat
output. A short overview what to make of the stat
numbers:
instructions
: This is the total number of instructions that the CPU had to execute to run the program. In general, less is better; but the actual run time is determined by how many instructions the CPU can execute in parallel (current CPUs can issue up to 4 instructions per cycle). You can see this ratio right behind the instructions
value, such as: 2,384,060,126 instructions # 1.59 insns per cycle
. The bigger that number the better. But it is really hard to judge the efficiency of a program by this number. (After all there could be unnecessarily long instruction sequences that have a high IPC, while a shorter sequence would be faster overall, but with a lower IPC.)branches
and branch-misses
Branches are places where the CPU cannot be certain which code needs to be executed next. But instead of waiting for the decision the CPU predicts the jump address and starts executing. If it then determines it miss-predicted it has to roll back its work and start again from the correct address. Therefore miss-predictions are costly and should be reduced to a minimum. The branch miss-prediction ratio is shown in the perf
output.page-faults
A page fault happens when a memory address is accessed that has not yet been allocated in physical memory by the operating system. In that case the TLB (translation lookaside buffer) entry cannot be determined by the CPU and the OS has to interrupt the execution of the program to allocate the page and fill it in the page table for use in the TLB. Page-faults must happen after memory allocations (unless malloc
is able to reuse previously deallocated memory) and are therefore an indicator for "irresponsible" memory allocations.Valgrind includes a tool that can profile your code, which should compiled with debug symbols and optimization turned on. This can be easily achieved in SMASH by specifying the -DCMAKE_BUILD_TYPE=RelWithDebInfo
CMake option when setting up the project. Once compiled SMASH in this mode, run
valgrind --tool=callgrind ./smash
from the build directory. Note that this tool is great and accurate, but it will make the execution of your code extremely slow. For one SMASH event with the default configuration file, the execution time will pass from few dozens seconds to the realm of (tens of) minutes, roughly speaking. Once terminated, the run generates a file called callgrind.out.X, where X usually is the process ID. Use the kcachegrind
tool to read this file. It will give you a graphical analysis of the profiling output with results like which lines cost how much. Alternatively, you can use gprof2dot
, which is a more general tool to visualize the output of different profilers (see below).
It is worth remarking that using the Profiling
build for this type of measurement will make calls to mcount
appear in the calling graph, but this is an artefact of the profiling procedure, which might even affect measured performance and, hence, should be avoided.
This is a very nice way to visualize profilers output. The software is written in Python and open-source. You can install it via pip
, e.g. via
pip install --user gprof2dot
for a user-only installation. Then you are ready to use it. Check out the README examples as quick-start. To produce a graph out of the Valgrind output, use something like
gprof2dot --strip -f callgrind callgrind.out.X | dot -Tsvg -o output.svg
where you need to replace X
by the proper number and you can choose a better name for the produced SVG output file. The --strip
option will
strip function parameters, template parameters, and const modifiers from demangled C++ function names
and it is encouraged to be used to obtain a more readable result with SMASH.
Flame graphs are a useful way to visualize the call graph output of a profiler. To generate them, do:
git clone https://github.com/brendangregg/FlameGraph export FGPATH=$(pwd)/FlameGraph
This gives you a collection of Perl scripts for flame graph generation. Different profilers are supported. In the following example we are using perf on Linux:
perf record -F 99 -g ./smash perf script | $FGPATH/stackcollapse-perf.pl > out.perf-folded $FGPATH/flamegraph.pl out.perf-folded > perf.svg firefox perf.svg
If you get mangled names of the functions, try this as a second line
perf script | c++filt | $FGPATH/stackcollapse-perf.pl > out.perf-folded
The x axis represents the total duration that the corresponding stack frame lived for. The order and the colors are arbitrary and optimized for readability. The y axis represents the position on the stack.
Containers for SMASH are built and shipped next to each new public release. Instructions about how to build them can be found here.