Using Submodule Files in Fortran Applications - GEOS-ESM/MAPL GitHub Wiki
As a module grows larger, the only way to modularize it is to break it into several modules. This exposes the internal structure, raising the potential for unnecessary global name clashes and giving the user of the module access to what ought to be private data and/or procedures.
In addition, if a change is made to the code inside a module procedure, even a private one, a recompilation of of every file which accesses the module, directly or indirectly, is necessary.
To avoid the above issues, a separate program unit (submodule) is introduced (Fortran 2008) to allow a module procedure to have its interface defined in a module while its body is provided in the submodule.
Definition
A submodule is a program unit that extends a module or another submodule. It may contain the definitions of procedures declared in a module or another submodule.
Submodules can access the things declared in their parent module or submodule by host association - the source code can still access PRIVATE things in the same way that it could if it was still part of the physical source code of the parent.
A change in a submodule cannot alter an interface in the module.
The submodule feature allows the separation of:
- routine and variable declarations (Fortran interfaces), and
- routine implementations.
A submodule contains the implementations of routines. This can also serve as a purpose of hiding the source for the module from entities who have no need to know it. Another advantage of submodules is that they prevent compilation cascades in situations of complex dependencies.
The entities (variables, types, procedures) declared by the submodule are local to that submodule, with the sole exception of separate module procedures that are declared in the ancestor module and defined in the submodule.
Access to parent's entities
A submodule can access the entities from its parent module or submodule by host association.
Unlike a module, a submodule cannot be referenced in other program units by use association.
Entities declared in a submodule are only accessible from the submodule and its descendent submodules.
Therefore, a submodule of a module does not USE
the parent module.
The submodule statement that starts a submodule program unit identifies the module (and perhaps another submodule) that it extends.
Naming submodules
- A submodule cannot have the same name as other global entities.
- The name of a submodule can be the same as the name of another submodule if they do not have the same ancestor module.
Compilation
A parent module can be broken into submodules to avoid compilation cascades, if relevant to the processor being used. Changes to a module require recompilation of that module (and its descendants), and then recompilation of all program units that use that module (and their descendants). Therefore, because of the cascading effect, we will need to recompile all the descendant modules of the modified modules.
Even if a module procedure implementation in a submodule is changed, as long as the module procedure interface declared in the ancestor module remains the same, a recompilation would not be needed for the user of the module. One of the main advantages of using submodules is that when one submodule is changed, recompilation is only needed for the submodule and its descendant submodules. Submodules subsequently speed up the build process in addition minimising the number of files that are affected during a change.
Submodules don’t make your code any faster (nor any slower), but they can lead to a dramatic improvement in programmer productivity through better partitioning and reduced build times.
Splitting a module into submodules can also reduce the memory requirement during compilation.
Compilation statistics for OuterMetaComponent with submodule files
We first compile the entire MAPL. Then we touch
the OuterMetaComponent.F90
files and the OuterMetaComponent
related submodules files. We measure the time it takes to recompile.
Compiler | discover |
bucy |
---|---|---|
gfortran | 31.978 | 25.612 |
nagfor | 23.324 | 17.088 |
ifort | 34.951 | 32.370 |
ifx | 30.525 | 30.307 |
Compiler | discover |
bucy |
---|---|---|
GNU 13.3.0 | 54.58 | 44.73 |
GNU 14.2.0 | 55.42 | 44.42 |
Intel 2021.13.0 | 43.30 | |
Intel 2024.2.0 | 44.52 | |
NAG 7.2-7215 | 210.07 | |
NAG 7.2.06 | 52.77 |
StateRegistry
Compilation statistics for StateRegistry
without submodules
The file StateRegistry.F90
defines a module that has 31 internal procedures
spanning over 853 lines of code. On bucy
(more consistent than discover
),
we compile (debugging mode) the entire MAPL code and record the time it took
to compile StateRegistry.F90
.
Compiler | Time (s) |
---|---|
gfortran | 2.977 |
nagfor | 20.902 |
ifort | 1.962 |
ifx | 1.774 |
Note that the above timing numbers are the same whether we do a serial or multi-thread compilation.
StateRegistry
with submodules
We split the StateRegistry
module into 28 submodules files.
The new StateRegistry.F90
file now has 327 lines of code.
We want to examine how the submodules affect compilation.
In particular, we want to answer the following questions:
- Does the time it takes to compile individual submodule files less than the time to compile the parent module?
- Do the submodule files facilitate the multi-threading compilation?
The table below contains the times it took to compile (with 8 threads) each of the submodule files together with
StateRegistry.F90
for the four compilers.
The compilation statistics were generated with ninja
.
We can make a couple of quick observations:
- The new
StateRegistry.F90
file takes less time to compile than the original one, with a significant reduction fornagfor
. - Some submodule files requires more compilation time than the parent module.
- No submodule file is more compilation consuming than the original parent module.
- Compiling all the files (
StateRegistry.F90
and associated submodules) takes more time than the originalStateRegistry.F90
file even in a multi-threading environment.
Files | gfortran | nagfor | ifort | ifx |
---|---|---|---|---|
StateRegistry.F90 |
2.247 | 0.362 | 0.927 | 1.058 |
add_family.F90 | 0.253 | 0.391 | 0.955 | 1.081 |
add_primary_spec.F90 | 0.289 | 0.429 | 1.022 | 1.130 |
add_spec.F90 | 0.286 | 1.134 | 0.428 | 1.012 |
add_subregistry.F90 | 0.268 | 0.381 | 0.940 | 1.080 |
add_to_states.F90 | 1.000 | 1.169 | 1.576 | 1.582 |
add_virtual_pt.F90 | 0.256 | 0.377 | 0.953 | 1.077 |
allocate.F90 | 0.257 | 0.381 | 0.995 | 1.125 |
extend.F90 | 0.338 | 0.447 | 1.182 | 1.158 |
filter.F90 | 0.257 | 1.888 | 0.936 | 1.081 |
get_export_couplers | 0.261 | 2.621 | 1.025 | 1.131 |
get_extension_family.F90 | 0.255 | 0.351 | 0.940 | 1.088 |
get_extensions.F90 | 0.252 | 0.366 | 0.980 | 1.101 |
get_import_couplers.F90 | 0.252 | 2.655 | 0.997 | 1.121 |
get_primary_extension.F90 | 0.249 | 0.354 | 1.008 | 1.112 |
get_subregistry_by_conn_pt.F90 | 0.246 | 0.350 | 0.935 | 1.074 |
get_subregistry_by_name.F90 | 0.256 | 0.355 | 0.942 | 1.071 |
has_subregistry.F90 | 0.251 | 0.351 | 0.953 | 1.054 |
has_virtual_pt.F90 | 0.247 | 0.366 | 0.946 | 1.079 |
link_extension.F90 | 0.247 | 0.353 | 0.995 | 1.136 |
num_owned_items.F90 | 0.246 | 0.349 | 0.928 | 1.059 |
propagate_exports_all.F90 | 0.253 | 0.361 | 0.935 | 1.085 |
propagate_exports_subregistry.F90 | 0.253 | 1.908 | 0.953 | 1.084 |
propagate_exports_virtual_pt.F90 | 0.69 | 0.363 | 0.951 | 1.069 |
propagate_unsatisfied_imports_all.F90 | 0.249 | 0.359 | 0.936 | 1.113 |
propagate_unsatisfied_imports_subregistry.F90 | 0.259 | 1.917 | 1.021 | 1.065 |
propagate_unsatisfied_imports_virtual_pt.F90 | 0.266 | 0.359 | 1.024 | 1.156 |
set_blanket_geometry.F90 | 0.250 | 2.625 | 1.000 | 1.156 |
write_formatted.F90 | 0.263 | 3.469 | 1.038 | 1.150 |
Compiling one file only
We are attempting to compile the following program included a single file:
subroutine subA()
end subroutine subA
We record the time it takes to compile the program on bucy
.
Compiler | Option | bucy |
discover |
||||
---|---|---|---|---|---|---|---|
real | user | sys | real | user | sys | ||
nagfor | none | 0.050 | 0.025 | 0.020 | 0.111 | 0.033 | 0.027 |
-O0 |
0.046 | 0.024 | 0.021 | 0.059 | 0.030 | 0.024 | |
-O2 |
0.047 | 0.028 | 0.018 | 0.061 | 0.036 | 0.019 | |
-O3 |
0.050 | 0.031 | 0.019 | 0.060 | 0.019 | 0.036 | |
gfortran | none | 0.025 | 0.010 | 0.012 | 0.219 | 0.018 | 0.033 |
-O0 |
0.023 | 0.015 | 0.008 | 0.033 | 0.022 | 0.009 | |
-O2 |
0.042 | 0.014 | 0.010 | 0.052 | 0.015 | 0.021 | |
-O3 |
0.024 | 0.016 | 0.008 | 0.033 | 0.024 | 0.008 | |
ifx | none | 0.055 | 0.022 | 0.025 | 0.514 | 0.024 | 0.104 |
-O0 |
0.052 | 0.017 | 0.030 | 0.098 | 0.011 | 0.071 | |
-O2 |
0.050 | 0.022 | 0.027 | 0.090 | 0.012 | 0.072 | |
-O3 |
0.049 | 0.018 | 0.030 | 0.094 | 0.023 | 0.062 | |
ifort | none | 0.047 | 0.012 | 0.031 | 0.391 | 0.000 | 0.132 |
-O0 |
0.040 | 0.010 | 0.030 | 0.092 | 0.016 | 0.062 | |
-O2 |
0.042 | 0.011 | 0.031 | 0.087 | 0.016 | 0.065 | |
-O3 |
0.081 | 0.015 | 0.028 | 0.091 | 0.012 | 0.070 |
Here are the compilations statistics when we only compile the new StateRegistry.F90
and the related submodule files with 8 threads.
Compiler | discover |
bucy |
---|---|---|
gfortran | 13.654 | 8.354 |
nagfor | 20.568 | 12.343 |
ifort | 22.114 | 13.756 |
ifx | 17.928 | 12.929 |
References
- Submodules (Fortran 2008), IBM Open XL Fortran for AIX.
- SUBMODULE, Intel
- What are submodules and how are they used?, stackoverflow.com, April 2014.
- We All Live in a Yellow Submodule, Doctor Fortran, July 2015.
- The new features of Fortran 2008, John Reid, May 2010.
- Progress on Fortran 2003 and the Technical Report on Enhanced Module Facilities, John Reid, September 2021.
- Fortran Wiki - Submodules, April 2014.