3 - Compilation Basics
Programming languages can be differentiated into Interpreted and Compiled languages. Languages such as Python are interpreted languages, where the code is read directly by the computer and the actions described are performed. In contrast, languages such as Fortran must be compiled by a program before the code can be used, where a program known as a compiler reads through the code, converting it to machine language. While time must be spent compiling the code, this generally results in very noticeable performance increases and is the reason why most high performance codes are compiled. Readers familiar with Python may be familiar with the numpy
library that is often used for numerical calculations, a library which makes use of compiled codes to give Python codes some dramatic reductions in numerical computation time.
Compiled Codes and CMake
This project uses CMake
for building and compiling.
CMake
is a tool commonly used for C/C++ and Fortran projects
in order to allow for easy compilation of the project in multiple operating systems (Linux, OSX, Windows, etc.),
using various compilers (GCC, Clang, Intel, etc.) with potentially different levels of optimisations (Debug, Release, etc.), all at the same time.
The way CMake
works is by reading a CMakeLists.txt
file, which is a text file that contains the commands to be executed by CMake
. CMake
then scans for the project's dependencies and builds the
necessary files e.g. Makefile
s to compile the project. You then use the newly generated files to compile the project.
A normal project being built with CMake
will look something like this:
mkdir build
cd build
cmake .. # This is relative to top level directory, containing CMakeLists.txt
make all # Assuming we are using GNU Makefiles else use cmake --build .
The greater discussion on build tools and more advanced forms of compilation is deferred for the later sections, see 7 - Build Tools.
Build tools such as CMake
are particularly useful in a compiled code that utilises OOP as it allows for easy compilation of the required files since it automatically deduces the order of dependencies, thus removing this burden from the programmer.
The code snippet below comes from the CMakeLists.txt
used in the project.
set(SOURCES
src/Constants.F90
src/Materials.F90
src/Problem.F90
src/Output.F90
src/Matrix_Base.F90
src/CRS.F90
src/Solver.F90
src/MatGen.F90)
Try and change the compilation order of the files in the SOURCES
in CMakeLists.txt
to see how the CMake
can handle it gracefully. Run the following to reconfigure the project:
cd build
cmake ..
make all
The diffusion
binary can be found under build/bin/diffusion
and can be run with the following command:
./build/bin/diffusion
NOTE: make sure you have an
input.in
file in the directory where you are runningdiffusion
from.
Debug and Release builds
One of the most appealing features of CMake
is the ability to build the project in different modes
side-by-side. This allows for easy switching between different modes of compilation, e.g. Debug and Release.
This project is by default built in Release mode, which enables various optimisations to make the code run faster. Let us try and build the project in Debug mode. From the root directory of the project run the following commands:
mkdir debug
cd debug
cmake .. -DCMAKE_BUILD_TYPE=Debug
make all
That is all there is to it! The diffusion
binary in the Debug
build can be found under debug/bin/diffusion
and can be run with like the normal diffusion
binary.
NOTE: make sure you have an
input.in
file in the directory where you are runningdiffusion
from.
Compiler Directives
There will be instances where one might wish to pass certain options to the compiler, e.g. to disable a warning, or to enable
some experimental optimisations or toggle a preprocessor macro. This can be done via the CMakeLists.txt
file but also
through the command-line interface of CMake
. Here the latter is demonstrated.
Say that in our Debug
build we wish to disable the verbose debug output found in Solve.F90
#ifdef DEBUG ! If DEBUG macro is defined, then compile the following code
write(*,*) "---CG Convergence Succeeded---"
write(*,'(g0)',advance='no') "Succeeded after iterations: "
write(*,'(g0)',advance='no') BCG_Iterations
write(*,'(g0)',advance='no') " with residual:"
write(*,'(E14.6)') rho1
write(*,*) "-------------------------------"
#endif
As seen above, the #ifdef
directive is used to enable or disable the code in the file.
When we build the project in Debug mode, we also define the DEBUG
preprocessor macro in our CMakeLists.txt
.
To prevent that snippet of code from compiling, normally we would pass the -UDEBUG
option to the compiler.
In CMake
that can be achieved via
cd debug
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_Fortran_FLAGS=-UDEBUG
make all
To test that it worked run the following and observe that the output is missing the
---CG Convergence Succeeded---
... lines
./debug/bin/diffusion
NOTE: make sure you have an
input.in
file in the directory where you are runningdiffusion
from.