Compiling an application
Where to compile an application
You can compile an applications either directly on a login node or in a batch job. If you do not need many cores and a lot of memory, then compiling on the login node is fine. If you need many cores or a lot of memory, then we recommend that you compile your code within a batch job or a batch interactive job.
Choosing a compiler
The choice of a compiler is not always easy. If you are compiling a third-party application, you should check its requirements to see which compilers are supported. Most open-source software projects coming from the Linux world are built with, and therefore support, GNU compilers (gcc). Although the majority of these applications is backward-compatible with older gcc compilers, a few "bleeding edge" applications require the very latest version of gcc and will not work with any other compiler.
- gcc 4.8.5 is supporting most features of the c++11 standard
- gcc 6.3.0 is supporting most features of the c++14 standard
- gcc 8.2.0 is supporting most features of the c++17 standard
If you are writing your own programs, we recommend that you use (or at least try) multiple compilers:
- this ensures that your program is not using non-standard language extensions, thus making it more portable
- it makes it easier to find bugs in your code, as some compilers are better at finding potential errors than others — assuming that you pay attention to the warnings issued by the compiler
- it may have a huge impact on your code's performance, as some compilers may be able to perform better optimization than others
A common mistake done by new users is to believe that all compilers are equal. This is not the case. Different compilers — even different releases of the same compiler — will produce different binary code, which in turn will produce different numerical results. In addition, aggressive compiler optimizations are most likely to lead to different — sometimes incorrect! — results. It is therefore critically important to check the results of your computations when using different compilers, or different optimization options.
Note that there is no such thing as a universal compiler. Similarly, there is not universal set of optimization options that will provide the best performance with all applications. It is only through experiments that you will be able to find which combination of compiler and optimizations is best for a particular application.
Compilers available on Euler
- gcc: 4.8.5, 6.3.0, 8.2.0
- intel: 18.0.1, 19.1.0, 2022.1.2
- pgi: 15.1
- llvm: 5.0.0, 6.0.0
Simple program (sequential)
Once you have loaded the compiler of your choice, you can invoke it to compile a program and generate an executable using a command like:
module load compiler compiler program -o executable
Note that you do not need to load anything to use one of the standard GNU compilers (gcc, g++ or gfortran) that are part of the operating system.
Parallel program (openMP)
All compilers installed on Brutus support openMP, although it is not always enabled by default. The compiler flags to enable openMP are:
- GNU: -fopenmp
- Intel: -openmp (type ifort -help openmp for a list of all openMP-related options)
- PGI: -mp
Note that these must be specified both during the compilation and linking stages.
Parallel program (MPI)
The compilation of parallel applications based on the Message Passing Interface (MPI) is slightly more complicated. Once you have loaded the compiler of your choice, you must also decide which MPI library you want to use. Two MPI libraries are available on Brutus:
- Open MPI (recommended)
Applications compiled with Open MPI or MVAPICH2 run on nodes connected to the InfiniBand network.
Open MPI is recommended for all applications. MVAPICH2 is provided for applications that are not compatible (or do not run well) with Open MPI.
Two series of modules — open_mpi and mvapich2 — are available to configure your environment for a particular MPI library. In addition, these modules define wrappers — e.g. mpicc, mpif90 — that greatly simplify the compilation of MPI applications. These wrappers are compiler-dependent and invoke whichever compiler was active (loaded) when you loaded the MPI module. For this reason, the MPI module must absolutely be loaded after the compiler module.
To summarize, the compilation of an MPI application should look somewhat like this:
module load compiler module load MPI library mpicc program -o executable ← C program mpiCC program -o executable ← C++ program mpif77 program -o executable ← Fortran 77 program mpif90 program -o executable ← Fortran 90 program
User manuals and documentation about MPI are available here.
Most open-source applications have some built-in mechanism to automatically configure themselves for the platform where you want to compile them. The procedure to configure and install these applications often consists of just three commands: configure, make and make install. This procedure works on Brutus too. The only thing you must keep in mind, is to load the compiler of your choice first:
module load compiler configure --prefix installation directory [other options] make make install
In most cases, configure will detect and use the compiler you have loaded, and "do the right thing".
If the application you want to install does not provide automatic configuration, chances are that you will have to configure it manually. Depending on the application, this can be done by setting some environment variables or editing a Makefile to indicate which compilers and libraries to use, and where they are located. This is really application-dependent and is therefore beyond the scope of this article. One thing to keep in mind though, is that you should never need to give the full path to a compiler or library; the corresponding environment modules should take care of that.