Fortran coarrays
last modified November 14, 2026
A coarray is an array whose extra dimensions are spread over several
images, that is, several running copies of the same program, each with
its own memory. The images exchange data by naming the copy they want, as in
x[2], and synchronise with sync all. Unlike OpenMP,
where the threads share one address space, coarrays are a model of distributed
memory that works on one processor and on a cluster alike, and the language
itself, rather than a library, provides the communication.
This article declares coarrays, reads the number of images and the number of
the current one, accesses another image's copy, synchronises the images,
collects values with co_sum, co_max,
co_min and co_reduce, guards a critical section with
a construct, splits a loop over the images and computes pi from the terms each
image summed. The programs are compiled with -fcoarray=single,
which runs the whole thing as one image; building for several images is
explained, and shown to need a library that is not installed here.
Images
A coarray is declared with square brackets after the usual bounds:
integer :: value[*] gives every image one scalar named
value. The bracket holds the codimensions, and an
asterisk means that the extent follows the number of images, so the array has
one element on each of them. num_images() returns how many images
are running and this_image() the number of the current one,
counting from one.
program co1
implicit none
integer :: value[*]
integer :: i, total
print '(a, i0)', 'images: ', num_images()
print '(a, i0)', 'this image: ', this_image()
value = this_image() * 10
sync all
print '(a, i0, a, i0)', 'image ', this_image(), ' holds ', value
total = 0
do i = 1, num_images()
total = total + value[i]
end do
print '(a, i0)', 'the total over all images: ', total
end program co1
Each image writes its own value and, after the barrier, reads the
copies of all images by putting the image number in brackets, as in
value[i]. With one image the loop has a single pass and the total
is 10. With four images the four values would be 10, 20, 30 and 40, the loop
would read all four, and the total would be 100; the source does not change,
only the number of images does.
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co1.f90 -o co1 $ ./co1 images: 1 this image: 1 image 1 holds 10 the total over all images: 10
A coarray with ordinary dimensions
The two kinds of dimension mix freely. real :: row(4)[*] is a
four-element array on every image, and the codimension adds the image axis
behind it. lcobound and ucobound report the bounds of
the codimensions, and with the asterisk form they are one and the number of
images. Reading row(2)[1] takes the second element from image one.
program co3
implicit none
real :: row(4)[*]
integer :: i
row = real(this_image())
row(2) = 10.0 * this_image()
sync all
print '(a, i0)', 'this image: ', this_image()
print '(a, 4f6.1)', 'its own row: ', row
print '(a, f6.1)', 'row(2) on image 1: ', row(2)[1]
print '(a, i0)', 'lower codimension: ', lcobound(row)
print '(a, i0)', 'upper codimension: ', ucobound(row)
print '(a, i0)', 'size in coarray dimensions: ', ucobound(row) - &
lcobound(row) + 1
do i = 1, num_images()
print '(a, i0, a, f6.1)', 'image ', i, ' holds ', row(2)[i]
end do
end program co3
The array bounds and the codimension bounds are separate, which the output shows by printing a four-element row and a codimension of extent one. The loop at the end reads the same element from every image with a variable index in the brackets, the general form of a remote access. On four images every line of that loop would print the same number here, because all images set the element the same way from their own number only in the first assignment; the second assignment gives each copy a different value.
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co3.f90 -o co3 $ ./co3 this image: 1 its own row: 1.0 10.0 1.0 1.0 row(2) on image 1: 10.0 lower codimension: 1 upper codimension: 1 size in coarray dimensions: 1 image 1 holds 10.0
Synchronisation and critical sections
An assignment to a remote image is not visible to that image until a
synchronisation: sync all waits for every image to arrive and makes
all writes before it visible to all reads after it. Without a barrier a program
may read a stale value, and the result depends on timing. For a section that
only one image at a time may execute, Fortran has the critical
construct, which is a lock shared by all images.
program co4
implicit none
integer :: counter[*]
integer :: i
counter = 0
sync all
do i = 1, num_images()
if (i /= this_image()) counter[i] = counter[i] + 1
end do
sync all
critical
print '(a, i0, a, i0)', 'image ', this_image(), &
' sees a counter of ', counter
end critical
end program co4
The program increments the copies that belong to the other images, so each image ends with the number of images minus one, and prints its own copy inside a critical section so that the lines do not interleave. With one image no other copy exists, the loop is empty, and the counter stays at zero. The two barriers have different jobs: the first makes sure every copy starts at zero before anyone adds to it, the second makes the additions visible before they are read.
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co4.f90 -o co4 $ ./co4 image 1 sees a counter of 0
Collectives
A collective subroutine combines one value from every image and leaves
the result on every image. co_sum adds, co_max and
co_min reduce with the obvious operators, and
co_reduce takes a pure function of two arguments and folds the
values with it. All of them synchronise the images, so an explicit
sync all before the call is unnecessary.
program co2
implicit none
integer :: x, y
real :: r
logical :: ok
x = this_image() * 7
y = this_image() * 3
r = real(this_image())
ok = this_image() == 2
call co_sum(x)
call co_max(y)
call co_min(r)
call co_reduce(ok, both)
print '(a, i0)', 'sum: ', x
print '(a, i0)', 'max: ', y
print '(a, f0.1)', 'min: ', r
print '(a, l1)', 'some image was 2: ', ok
contains
pure logical function both(a, b) result(c)
logical, intent(in) :: a, b
c = a .or. b
end function both
end program co2
With one image each collective returns its argument unchanged: the sum of 7 is
7, the maximum of 3 is 3, the minimum of 1.0 is 1.0, and the reduction of
.false. is false. On four images the same program would print 70,
12, 1.0 and true, because the values are 7, 14, 21 and 28 for the sum, 3, 6, 9
and 12 for the maximum, and image two would make the logical reduction true. The
function given to co_reduce must be pure and associative, so that
the order in which the images are folded does not matter.
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co2.f90 -o co2 $ ./co2 sum: 7 max: 3 min: 1.0 some image was 2: F
Splitting the work
The usual shape of a coarray program is a loop over the local part of a global
range, followed by a collective. Each image computes the first and last index of
its share from this_image() and num_images(), so the
same source divides the work differently as the number of images changes. The
program below prints the share it would take and the totals it implies.
program co6
implicit none
integer, parameter :: n = 1000
integer :: chunk, first, last
chunk = (n + num_images() - 1) / num_images()
first = (this_image() - 1) * chunk + 1
last = min(first + chunk - 1, n)
print '(a, i0, a, i0)', 'images in the run: ', num_images(), &
', this one: ', this_image()
print '(a, i0, a, i0)', 'this image would process ', first, ' to ', last
print '(a, i0)', 'iterations assigned here: ', last - first + 1
print '(a, i0)', 'iterations in total: ', &
(last - first + 1) * num_images()
print '(a, i0)', 'the static chunk size: ', chunk
end program co6
With one image the share is the whole range of a thousand iterations. With four
images each would take two hundred and fifty, and with three the chunk size
rounds up to 334, so the last image would take only 332 and the min
would matter. The expression (n + images - 1) / images is the
integer ceiling of the division, the standard way to make the chunks cover the
range without a gap.
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co6.f90 -o co6 $ ./co6 images in the run: 1, this one: 1 this image would process 1 to 1000 iterations assigned here: 1000 iterations in total: 1000 the static chunk size: 1000
A calculation split over the images
The program below computes pi from the Leibniz series, whose terms are
4 * (-1)**(k+1) / (2k-1). Each image sums its own range into a
coarray scalar, co_sum adds the partial sums, and every image then
holds the same total and prints the same estimate. The series converges slowly,
so the result is a demonstration of the mechanics rather than a good way to
compute pi.
program co5
use iso_fortran_env, only: real64
implicit none
integer, parameter :: n = 10000000
integer :: k, chunk, first, last
real(real64) :: partial[*], term, pi
chunk = n / num_images()
first = (this_image() - 1) * chunk + 1
last = first + chunk - 1
partial = 0.0_real64
do k = first, last
term = 1.0_real64 / real(2 * k - 1, real64)
if (mod(k, 2) == 0) term = -term
partial = partial + term
end do
sync all
call co_sum(partial)
pi = 4.0_real64 * partial
print '(a, i0)', 'images: ', num_images()
print '(a, i0, a, i0)', 'this image summed terms ', first, ' to ', last
print '(a, f0.8)', 'pi from the series: ', pi
print '(a, es10.2)', 'the error: ', abs(pi - 4.0_real64 * atan(1.0_real64))
end program co5
Ten million terms give 3.14159255, an error of one in the seventh decimal, which
is what the alternating series leaves after that many terms. The line that
prints the range shows the share of this image, and with four images four
different ranges would be summed and combined, with the same final answer to
the last digit that the floating-point addition allows. The
sync all before the collective is redundant but harmless, and it is
kept to mark the end of the local phase.
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -fcoarray=single co5.f90 -o co5 $ ./co5 images: 1 this image summed terms 1 to 10000000 pi from the series: 3.14159255 the error: 1.00E-07
Building for more than one image
-fcoarray=single is the easy mode: the compiler implements the
coarray syntax in one process, which is enough to develop and test the logic but
never runs two images. For real parallelism gfortran needs
-fcoarray=lib and the OpenCoarrays runtime, which supplies
the communication functions and normally starts the images with MPI.
program co3
implicit none
real :: row(4)[*]
integer :: i
row = real(this_image())
row(2) = 10.0 * this_image()
sync all
print '(a, i0)', 'this image: ', this_image()
print '(a, 4f6.1)', 'its own row: ', row
print '(a, f6.1)', 'row(2) on image 1: ', row(2)[1]
print '(a, i0)', 'lower codimension: ', lcobound(row)
print '(a, i0)', 'upper codimension: ', ucobound(row)
print '(a, i0)', 'size in coarray dimensions: ', ucobound(row) - &
lcobound(row) + 1
do i = 1, num_images()
print '(a, i0, a, f6.1)', 'image ', i, ' holds ', row(2)[i]
end do
end program co3
Compiling the same program with -fcoarray=lib gets as far as the
linker, which reports every runtime function that the library should have
provided. The names are the application binary interface between the compiled
code and the runtime, and the missing one at the top is the registration call
that every coarray makes at start-up. Installing OpenCoarrays, or a compiler
that bundles a coarray runtime, is the step that turns these programs into
multi-image ones; nothing in the source changes.
$ gfortran -std=f2018 -fcoarray=lib -c co3.f90 -o co3.o $ gfortran co3.o -o co3lib /usr/bin/ld: co3.o: in function `_caf_init.1.0': co3.f90:(.text+0x64): undefined reference to `_gfortran_caf_register' /usr/bin/ld: co3.o: in function `MAIN__': co3.f90:(.text+0x95): undefined reference to `_gfortran_caf_this_image' /usr/bin/ld: co3.f90:(.text+0xc8): undefined reference to `_gfortran_caf_this_image' /usr/bin/ld: co3.f90:(.text+0xfc): undefined reference to `_gfortran_caf_sync_all' /usr/bin/ld: co3.f90:(.text+0x16d): undefined reference to `_gfortran_caf_this_image' /usr/bin/ld: co3.f90:(.text+0x3e1): undefined reference to `_gfortran_caf_get' /usr/bin/ld: co3.f90:(.text+0x5eb): undefined reference to `_gfortran_caf_num_images' /usr/bin/ld: co3.f90:(.text+0x74e): undefined reference to `_gfortran_caf_num_images' /usr/bin/ld: co3.f90:(.text+0x79f): undefined reference to `_gfortran_caf_num_images' /usr/bin/ld: co3.f90:(.text+0x927): undefined reference to `_gfortran_caf_get' /usr/bin/ld: co3.o: in function `main': co3.f90:(.text+0x99a): undefined reference to `_gfortran_caf_init' /usr/bin/ld: co3.f90:(.text+0x9c4): undefined reference to `_gfortran_caf_finalize' collect2: error: ld returned 1 exit status
Next steps
The OpenMP article reaches the same goal through
threads in one address space, and the
parallel loops article covers
do concurrent, which a coarray program can also use inside each
image. The arrays article explains the
dimensions that a codimension extends, the
modules article explains the use
of a standard module such as iso_fortran_env, and the
error handling article explains the
status of a failed link.
Source
Fortran 2018 standard (J3/18-007r1)
GNU Fortran manual: Coarray Programming
In this article we have declared coarrays with and without ordinary dimensions,
read the image number and the number of images, accessed a remote copy with an
index in brackets, synchronised with sync all and protected a
section with critical, combined values with
co_sum, co_max, co_min and
co_reduce, divided a loop over the images, computed pi from the
terms each image summed, and seen that a multi-image build needs a runtime
library that this machine does not have. The previous article,
OpenMP with gfortran, shares the work within one
process instead.
Author
List all Fortran tutorials.