Technical Reference: shum_thread_utils#

Introduction#

The f_shum_thread_utils module is a Fortran MODULE, designed to provide an abstracted interface to some OpenMP features. However, despite being written in Fortran, it is not expected to be called directly from Fortran. (In fact the f_shum_thread_utils module contains a PRIVATE statement, limiting the visibility of its routines within Fortran.)

Instead, the f_shum_thread_utils module should be called from C code. All of the routines within the shum_thread_utils module have an ISO_C_BINDING interface, and there is a corresponding header file (c_shum_thread_utils.h) containing the C prototypes.

The rationale for the f_shum_thread_utils module is as follows: there is no guarantee that runtime libraries used by an OpenMP implementation are compatible between vendors or indeed, between different versions of an implementation from the same vendor. This means that mixing code between different compiler vendors - or different versions of a compiler from the same vendor - can cause problems. Normally, one can ensure that all of the code in a specific language is compiled from the same compiler. However, when mixing languages, there are many cases when it is legitimate - or even required - to use compilers from different vendors. This is when problems may begin.

As an explicit example, it is known that mixing Fortran code compiled by the Intel ifort 12.0.4 20110427 compiler with C code compiled by the GNU GCC 6.3.0 compiler on Linux x86 systems is unsuccessful. In this case, all the individual units compile correctly, but upon linking of the final executable binary a fatal error is met due to ABI (Application Binary Interface) differences.

By utilising the f_shum_thread_utils module, we can ensure all the direct OpenMP references are contained within one language - in this case Fortran, the primary programming language of most of the code utilising SHUMlib. This ensures that only one OpenMP runtime implementation is used, and there can be no incompatibilities.

There is however some performance impact associated with utilising the f_shum_thread_utils module. Its use introduces extra function calls, which in itself adds overhead. Additionally, the fact that Fortran function calls will be used from within C may inhibit some opitimisations - inlining for example - as compilers tend to be less effective in applying these kind of optimisations across languages. Generally though, in the problematic cases where the OpenMP implementation runtimes are incompatible, the additional performance gained by being able to use OpenMP at all (albeit less efficiently than directly) greatly outweighs this overhead.

Because of these performance considerations, we want to avoid always using f_shum_thread_utils. As a consequence, we should use preprocessing in combination with an if-def to select f_shum_thread_utils code only in cases where we need it. Ideally, a native C OpenMP implementation should also be produced (also protected by preprocessing if-def). In this way, the correct desired behaviour can be selected at compile-time, allowing one of the following behaviours at runtime:

  • No OpenMP is used.

  • OpenMP is used through native C. (The compiler defines the [standard] _OPENMP pre-processing macro - through the nomal compiler switch selection process - which may have better performance, but be less portable)

  • OpenMP is used through f_shum_thread_utils. (Which may be more portable but less performant than through native C. The compiler defines _OPENMP; the user defines SHUM_USE_C_OPENMP_VIA_THREAD_UTILS)

Additionally, there are many coding standards rules which must be followed when using the f_shum_thread_utils module to ensure the performance impact is avoided, whilst continuing to maintain the highest possible portability of the code, and the ability to validate with automated testing.

See also: The thread_utils API documentation

Use of the thread_utils API (from C)#

This section will detail how to correctly use the shum_thread_utils module from within C code.

Inclusion of the header

To access the f_shum_thread_utils routines, the c_shum_thread_utils.h header must be included in your code as shown in the code example below.

#include <stdio.h>
#include <stdint.h>
#include "c_shum_thread_utils.h"

int main(void)
{
  printf("thread ID: %" PRId64 "\n", threadID());
  return 0;
}

This exposes the correct C prototypes corresponding to the Fortran implementation. There is no requirement to protect the code with the _OPENMP pre-processing macro, as none of the OpenMP is exposed directly to the C.

However, one may choose to use protect the inclusion anyway, perhaps to allow preprocessing to switching between f_shum_thread_utils, direct OpenMP, and no OpenMP - as shown in the example below.

#include <stdio.h>
#include <stdint.h>

#if defined(_OPENMP) && defined(SHUM_USE_OPENMP_VIA_THREADUTILS)
#include "c_shum_thread_utils.h"
#elif defined(_OPENMP) && !defined(SHUM_USE_OPENMP_VIA_THREADUTILS)
#include <omp.h>
#endif

int main(void)
{
  int tid;

#if defined(_OPENMP) && defined(SHUM_USE_OPENMP_VIA_THREADUTILS)
  tid = (int)threadID();
#elif defined(_OPENMP) && !defined(SHUM_USE_OPENMP_VIA_THREADUTILS)
  tid = omp_get_thread_num();
#else
  tid = 1;
#endif

  printf("thread ID: %d\n", tid);
  return 0;
}

Note of course that this is a highly contrived example - if the OpenMP header were available it would be perfectly safe to include it in non-OpenMP builds. Additionally, calls to find the thread number are safe regardless of whether OpenMP is enabled or not; and we have no parallel regions in this example anyway! But it does serve to illustrate conceptually how a more complex case may work.