Skip to content

Compilers

Introduction to Compilers

In the realm of software development, compilers play an indispensable role by translating human-readable source code into machine-executable instructions. Essentially, a compiler is a bridge connecting the abstract world of programming languages to the concrete world of machine code.

In the context of High-Performance Computing (HPC), compilers are even more crucial. The performance of an HPC application heavily depends on the efficiency of the generated machine code, and that's where compilers come into the picture. They perform a series of optimizations to translate your high-level code into low-level instructions that execute as fast as possible on the target hardware. The choice of compiler, and the way it's used, can significantly impact the speed and efficiency of HPC applications.

Compilers are also responsible for catching errors at compile time. This includes everything from syntax errors to certain types of logical errors, which can save developers a significant amount of time and headache.

A Catalogue of Pre-Installed Compilers

Our systems come with a set of pre-installed compiler suites, each having their own compilers for the C, C++, and Fortran languages. Below is a snapshot of these suites:

Compiler Suite C Compiler C++ Compiler Fortran Compiler
GNU GCC gcc g++ gfortran
Intel icc icpc ifort

Let's dive deeper into each suite and understand their unique features and usage.

Intel Compiler Suite

Intel compilers, known for their robustness, compatibility, and performance, are optimized for Intel's hardware architecture. This can lead to significant performance improvements.

For a comprehensive usage of the Intel compiler suite, you can load the iccifort or intel-compilers modules:

$ module whatis iccifort
$ module whatis intel-compilers

This command will provide details about the module and a link to its official Intel homepage.

GNU GCC Compilers

The GNU Compiler Collection (GCC) is a universal compiler suite known for its flexibility and wide language support, including C, C++, and Fortran.

To list the GCC versions available on your system, you can use the following command:

$ module spider GCC

This command will display a description and various versions of the GCC module.

Compiling and Running Code Examples

Let's look at some simple examples of how to compile and run code using both the Intel and GCC compilers. We'll use a simple program written in C, C++, and Fortran that calculates the factorial of a number using a recursive function.

#include <stdio.h>

unsigned long long factorial(unsigned int n)
{
    if (n == 0)
       return 1;
    return n * factorial(n - 1);
}

int main() {
    unsigned int number = 20;
    printf("Factorial of %u = %llu\n", number, factorial(number));
    return 0;
}

To compile and run this program using the Intel and GCC compilers, use the following commands respectively:

$ icc factorial.c -o factorial
$ ./factorial

$ gcc factorial.c -o factorial
$ ./factorial
#include<iostream>

unsigned long long factorial(unsigned int n)
{
    if (n == 0)
        return 1;
    return n * factorial(n - 1);
}

int main() {
    unsigned int number = 20;
    std::cout << "Factorial of " << number << " = " << factorial(number) << std::endl;
    return 0;
}

To compile and run this program using the Intel and GCC compilers, use the following commands respectively:

$ icpc factorial.cpp -o factorial
$ ./factorial
$ g++ factorial.cpp -o factorial
$ ./factorial
program factorial_calculator
    implicit none
    integer :: number = 10
    print *, "Factorial of ", number, " = ", factorial(number)
end program factorial_calculator

recursive function factorial(n) result(fact)
    implicit none
    integer, intent(in) :: n
    integer :: fact
    if (n == 0) then
        fact = 1
    else
        fact = n * factorial(n-1)
    end if
end function factorial

To compile and run this program using the Intel and GCC compilers, use the following commands respectively:

$ ifort factorial.f90 -o factorial
$ ./factorial
$ gfortran factorial.f90 -o factorial
$ ./factorial

Choosing the Right Compiler

Your choice of compiler should align with your specific requirements. Intel's compilers may offer better performance on Intel's hardware, while GCC's broad compatibility and extensive language support offer versatility.

Regardless of your choice, writing and optimizing your code effectively is equally important. Understanding the strengths and weaknesses of each compiler can aid in writing better code, and thus, making more informed decisions about your toolset.

Ultimately, both the Intel and GNU GCC compiler suites are robust tools, each with unique advantages. By understanding their usage, you can significantly enhance your software's performance and portability.

Common Errors and Troubleshooting

Compilers often provide error and warning messages to aid in the debugging process. These messages can be extremely helpful, but also a bit cryptic for the uninitiated. Here are a few common issues you might encounter and some tips on how to resolve them:

  1. Syntax Errors: These are the most common errors that occur while writing a program. They relate to the grammar of the programming language, such as missing semicolons or mismatched parentheses. Most compilers will give you the line number and a brief description of what it thinks the problem is.

  2. Type Errors: These occur when the data type you are working with doesn't align with what the operation expects. For instance, if you're trying to perform an arithmetic operation on a string and a number, the compiler will throw a type error.

  3. Linker Errors: Linker errors typically occur when the compiler is trying to combine all of your source files into an executable but can't find a definition for a function declaration, a symbol, or a library that was referenced.

When you encounter an error, the first step should always be to carefully read the message. Even if it seems obscure, it's providing crucial information about what went wrong. If the message isn't clear, a quick internet search of the exact text will often yield helpful results.

Compiler Best Practices

Here are some best practices to get the most out of your compiler:

  1. Use Optimization Flags: Both Intel and GCC compilers provide various optimization flags that can significantly enhance the performance of your code. For instance, -O2 or -O3 flags instruct the compiler to perform all optimization that do not involve a space-speed tradeoff.

  2. Debugging: Compilers like GCC and Intel offer options for debugging. Using flags like -g with GCC or -debug with Intel compilers will include debugging information in the executable file and turn off optimizations that might change the source code's behavior.

  3. Warning Flags: Warning flags like -Wall in GCC enable all warning messages and are helpful to catch potential issues that are not necessarily syntax errors but could lead to bugs.

  4. Understand Your Compiler: Each compiler has its own set of features, optimizations, and quirks. Spend time in understanding these specifics. Refer to the compiler's official documentation for a comprehensive overview.