Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ yarn-error.log*
# Local deployment home files
/.npm
/.config

# clangd
/Benchmark/.cache
1 change: 1 addition & 0 deletions Benchmark/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ add_benchmark(PWR080)
add_benchmark(PWR081)
add_benchmark(PWR082)
add_benchmark(PWR083)
add_benchmark(PWR087)
10 changes: 5 additions & 5 deletions Benchmark/src/PWR070.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

// Forward-declare the functions to benchmark
extern "C" {
void clamp_even_data_points_f(int n, double *X, int min_value, int max_value);
void clamp_even_data_points_improved_f(int n, double *X, int min_value,
int max_value);
void clamp_data_points_f(int n, double *X, int min_value, int max_value);
void clamp_data_points_improved_f(int n, double *X, int min_value,
int max_value);
}

// Size adjusted to fit execution on micro-seconds
Expand All @@ -18,7 +18,7 @@ static void FortranExampleBench(benchmark::State &state) {
auto X = OpenCatalog::CreateRandomVector<double>(N);

for (auto _ : state) {
clamp_even_data_points_f(N, X.data(), MIN_VALUE, MAX_VALUE);
clamp_data_points_f(N, X.data(), MIN_VALUE, MAX_VALUE);
benchmark::DoNotOptimize(X);
}
}
Expand All @@ -27,7 +27,7 @@ static void FortranImprovedBench(benchmark::State &state) {
auto X = OpenCatalog::CreateRandomVector<double>(N);

for (auto _ : state) {
clamp_even_data_points_improved_f(N, X.data(), MIN_VALUE, MAX_VALUE);
clamp_data_points_improved_f(N, X.data(), MIN_VALUE, MAX_VALUE);
benchmark::DoNotOptimize(X);
}
}
Expand Down
38 changes: 38 additions & 0 deletions Benchmark/src/PWR087.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "Benchmark.h"

// Forward-declare the functions to benchmark
extern "C" {
void clamp_even_data_points_f(int n, double *X, int min_value, int max_value);
void clamp_even_data_points_improved_f(int n, double *X, int min_value,
int max_value);
}

// Size adjusted to fit execution on micro-seconds
constexpr int N = 1024 * 1024;
constexpr double MIN_VALUE = 2.71;
constexpr double MAX_VALUE = 3.14;

#if OCB_ENABLE_Fortran

static void FortranExampleBench(benchmark::State &state) {
auto X = OpenCatalog::CreateRandomVector<double>(N);

for (auto _ : state) {
clamp_even_data_points_f(N, X.data(), MIN_VALUE, MAX_VALUE);
benchmark::DoNotOptimize(X);
}
}

static void FortranImprovedBench(benchmark::State &state) {
auto X = OpenCatalog::CreateRandomVector<double>(N);

for (auto _ : state) {
clamp_even_data_points_improved_f(N, X.data(), MIN_VALUE, MAX_VALUE);
benchmark::DoNotOptimize(X);
}
}

OC_BENCHMARK("PWR087 Fortran Example", FortranExampleBench);
OC_BENCHMARK("PWR087 Fortran Improved", FortranImprovedBench);

#endif
38 changes: 17 additions & 21 deletions Checks/PWR070/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,42 @@ When procedures receive arrays in explicit-shape or assumed-size form,
programmers are required to manually pass array properties as additional
arguments. This practice limits the compiler's ability to perform compatibility
checks, increasing the risk of difficult-to-diagnose runtime bugs.
Additionally, these types of arrays can result in suboptimal performance
compared to assumed-shape arrays.

### Actions

To improve both code safety and performance, transform explicit-shape and
assumed-size array dummy arguments to assumed-shape form.
To improve code safety, transform explicit-shape and assumed-size array dummy
arguments to assumed-shape form.

### Relevance

Fortran supports various methods for passing allocated arrays to procedures.
Typically, assumed-shape arrays should be preferred over explicit-shape and
assumed-size arrays, as they provide equivalent functionality while being safer
and more efficient:
assumed-size arrays, as they provide equivalent functionality while being
safer:

- **Assumed-shape arrays** are simply declared with a colon for each dimension;
e.g., `real :: arr(:)`, `real :: arr(:, :)`. They offer several benefits:
- **Assumed-shape arrays** are declared with a colon for each dimension; e.g.,
`real :: arr(:)`, `real :: arr(:, :)`. They offer several benefits:
- Compile-time checks for the compatibility of the passed array's rank.
- Automatic deduction of the size of each dimension from the passed array,
accessible via `shape(arr)` and `size(arr, dim)`.

- **Explicit-shape and assumed-size arrays** require manual specification of
dimension sizes as separate arguments, increasing the likelihood of errors:
dimension sizes, often as separate arguments, increasing the likelihood of
errors:
- Explicit-shape arrays specify the size of all dimensions; e.g., `real ::
arr(i, j)`.
- Assumed-size arrays leave the size of the last dimension unspecified; e.g.,
`real :: arr(n, *)`.
- In general, they lack compile-time checks for consistency between the
provided and the expected array.

Additionally, explicit-shape and assumed-size dummy arguments require contiguous
memory. This forces the creation of intermediate data copies when working with
array slices or strided accesses. In contrast, assumed-shape arrays can handle
these scenarios directly, leading to enhanced performance.
> [!NOTE]
> Additionally, explicit-shape and assumed-size dummy arguments require
> contiguous memory. The compiler may silently create contiguous temporary
> copies when passing non-contiguous data, such as array slices. In contrast,
> assumed-shape arrays can handle some of these scenarios directly, potentially
> leading to enhanced performance. See [PWR087](../PWR087/README.md) for more
> details.

### Code examples

Expand Down Expand Up @@ -172,17 +174,11 @@ $ ./a.out
Row 2 Sum: 6.00000000
```

> [!TIP]
> As explained previously, if the subroutines operate on a slice of the matrix,
> assumed-shape arrays can manage the slice directly, potentially improving
> performance.
>
> Check the PWR070 benchmark for a demonstration!

> [!WARNING]
> Beware that any procedures involving assumed-shape array arguments must have
> explicit interfaces at the point of call. If not, the updated code won't
> compile.
> compile. `sum_rows_assumed_shape()` provides an explicit interface due to
> being contained inside its caller.
>
> Check the [PWR068 entry](../PWR068/) for more details on implicit and explicit
> interfaces!
Expand Down
6 changes: 3 additions & 3 deletions Checks/PWR070/benchmark/example.f90
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ subroutine clamp_values_f(n, X, min_value, max_value)
end subroutine clamp_values_f
end module mod_explicit_shape

subroutine clamp_even_data_points_f(n, X, min_value, max_value) bind(c)
subroutine clamp_data_points_f(n, X, min_value, max_value) bind(c)
use iso_c_binding, only : c_double, c_int
use mod_explicit_shape, only : clamp_values_f

Expand All @@ -32,5 +32,5 @@ subroutine clamp_even_data_points_f(n, X, min_value, max_value) bind(c)
real(kind=c_double), dimension(n), intent(inout) :: X
real(kind=c_double), intent(in), value :: min_value, max_value

call clamp_values_f(n/2, X(2:n:2), min_value, max_value)
end subroutine clamp_even_data_points_f
call clamp_values_f(n, X, min_value, max_value)
end subroutine clamp_data_points_f
6 changes: 3 additions & 3 deletions Checks/PWR070/benchmark/solution.f90
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ subroutine clamp_values_improved_f(X, min_value, max_value)
end subroutine clamp_values_improved_f
end module mod_assumed_shape

subroutine clamp_even_data_points_improved_f(n, X, min_value, max_value) bind(c)
subroutine clamp_data_points_improved_f(n, X, min_value, max_value) bind(c)
use iso_c_binding, only : c_double, c_int
use mod_assumed_shape, only : clamp_values_improved_f

Expand All @@ -31,5 +31,5 @@ subroutine clamp_even_data_points_improved_f(n, X, min_value, max_value) bind(c)
real(kind=c_double), dimension(n), intent(inout) :: X
real(kind=c_double), intent(in), value :: min_value, max_value

call clamp_values_improved_f(X(2:n:2), min_value, max_value)
end subroutine clamp_even_data_points_improved_f
call clamp_values_improved_f(X, min_value, max_value)
end subroutine clamp_data_points_improved_f
181 changes: 181 additions & 0 deletions Checks/PWR087/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# PWR087: Declare array dummy arguments as assumed-shape arrays to favor compiler optimizations

### Issue

Assumed-shape dummy arguments require calls to be made through an explicit
interface so the caller can pass the array's runtime descriptor (describing
bounds, strides, etc.). With this information available, the compiler may
generate more efficient code and might avoid creating expensive temporary
copies when passing non-contiguous actual arguments (e.g., array slices).

### Actions

Transform explicit-shape and assumed-size array dummy arguments to
assumed-shape form to favor compiler optimizations.

### Relevance

Fortran supports various methods for passing allocated arrays to procedures.
Typically, assumed-shape arrays should be preferred over explicit-shape and
assumed-size arrays, as they give the compiler more opportunities to check and
optimize calls:

- **Assumed-shape arrays** are declared with a colon for each dimension; e.g.,
`real :: arr(:)`, `real :: arr(:, :)`:
- Procedure calls require an explicit interface because the caller implicitly
passes the array's runtime descriptor (describing properties such as array
bounds, strides, etc.).

- **Explicit-shape and assumed-size arrays** require manual specification of
dimension sizes, often as separate arguments:
- Explicit-shape arrays specify the size of all dimensions; e.g., `real ::
arr(i, j)`.
- Assumed-size arrays leave the size of the last dimension unspecified; e.g.,
`real :: arr(n, *)`.
- Procedure calls can be made through implicit interfaces.

Overall, assumed-shape arrays introduce the following benefits:

1. **Mandatory explicit interface:** The compiler has more information about
the called procedure and its arguments (types, intents, ranks, etc.), which can
facilitate compiler optimizations.

2. **Avoidance of hidden array temporaries:** With explicit-shape and
assumed-size dummy arguments, passing a non-contiguous actual argument may
cause the compiler to silently create a contiguous temporary copy, which can be
expensive. Assumed-shape arrays can avoid this for certain scenarios (e.g.,
regularly strided array sections) because the array descriptor describes how to
traverse memory.

> [!TIP]
> Many compilers provide flags to warn about these hidden copies. For
> example, `gfortran` supports `-Warray-temporaries`.

> [!TIP]
> Strided memory access is not always faster than packing data into a
> contiguous temporary; for example, a unit stride access is often more
> favorable for vectorization. Ultimately, the best choice depends on the
> computational kernel and the data. If a kernel benefits from packing, it can
> be forced even for an assumed-shape dummy argument by adding the `contiguous`
> attribute.

#### Summary

In terms of performance optimization:

- **Contiguous actual arguments:** Assumed-shape performance is typically on
par with explicit-shape and assumed-size arrays.

- **Non-contiguous actual arguments:** Performance depends on whether direct
strided access or packing is better for the kernel. If packing is better,
consider using, for example, an assumed-shape array with the `contiguous`
attribute.

> [!NOTE]
> Assumed-shape arrays significantly improve code quality and
> maintainability, making them worthwhile even if performance gains are not
> obtained. See [PWR070](../PWR070/README.md) for more details.

### Code examples

The following example shows a function that processes an array to clamp its
values to a minimum and maximum value. The initial example uses an
explicit-shape array dummy argument; note how the size `n` of the array is
passed as an additional argument to `clamp_values()`:

```fortran {5,9,10} showLineNumbers
! example.f90
module example
implicit none
contains
subroutine clamp_values(n, X, min_value, max_value)
use iso_fortran_env, only: real32

implicit none
integer, intent(in) :: n
real(kind=real32), dimension(n), intent(inout) :: X
real(kind=real32), intent(in) :: min_value, max_value

integer :: i

do i = 1, n
if(X(i) < min_value) then
X(i) = min_value
else if(X(i) > max_value) then
X(i) = max_value
end if
end do
end subroutine clamp_values
end module example
```

Making the switch to an assumed-shape array is as simple as dropping the `n`
argument, changing `X` to an assumed-shape dummy argument, and querying its
length using the `size()` intrinsic:

```fortran {5,9,14} showLineNumbers
! solution.f90
module solution
implicit none
contains
subroutine clamp_values(X, min_value, max_value)
use iso_fortran_env, only: real32

implicit none
real(kind=real32), dimension(:), intent(inout) :: X
real(kind=real32), intent(in) :: min_value, max_value

integer :: i

do i = 1, size(X, 1)
if(X(i) < min_value) then
X(i) = min_value
else if(X(i) > max_value) then
X(i) = max_value
end if
end do
end subroutine clamp_values
end module solution
```

> [!TIP]
> Now that `clamp_values()` uses an assumed-shape dummy argument, its
> performance might improve when calling the procedure to operate on
> non-contiguous data.
>
> Check the PWR087 benchmark for a demonstration!

> [!WARNING]
> Beware that any procedures involving assumed-shape array arguments
> must have explicit interfaces at the point of call. If not, the updated code
> won't compile. `clamp_values()` provides an explicit interface to callers due
> to being inside a `module`.
>
> Check the [PWR068 entry](../PWR068/) for more details on implicit and
> explicit interfaces!

### Related resources

- [PWR087
examples](https://github.com/codee-com/open-catalog/tree/main/Checks/PWR087/)

### References

- ["Arrays — Fortran Programming
Language"](https://fortran-lang.org/learn/best_practices/arrays/), Fortran
Community. [last checked February 2026]

- ["Passing arrays to subroutines in Fortran: Assumed shape vs explicit
shape"](https://stackoverflow.com/questions/75051887/passing-arrays-to-subroutines-in-fortran-assumed-shape-vs-explicit-shape),
Stack Overflow Community. [last checked February 2026]

- ["Fortran Modernisation
Workshop"](https://blog.rwth-aachen.de/hpc_import_20210107/attachments/39157901/39420371.pdf),
The Numerical Algorithms Group. [last checked February 2026]

- ["-Warray-temporaries"](https://gcc.gnu.org/onlinedocs/gfortran/Error-and-Warning-Options.html#index-Warray-temporaries),
Free Software Foundation, Inc. [last checked February 2026]

- ["Assumed-shape arrays
repacking"](https://flang.llvm.org/docs/ArrayRepacking.html), The Flang Team.
[last checked February 2026]
Loading