Fortran's DO CONCURRENT: Telling the Compiler It's Safe to Go Fast
DO CONCURRENT lets you promise the compiler that loop iterations are independent, unlocking vectorization and parallel execution without OpenMP directives — if you can keep that promise.
02 Dec 2025, 04:39 UTC

Here's a frustrating situation: you write a perfectly simple Fortran loop, the compiler refuses to vectorize it, and the optimization report mutters something about "possible data dependence." The compiler isn't being stubborn — it's being correct. A plain DO loop promises nothing about whether iteration 5 depends on iteration 4, so the compiler must assume the worst. DO CONCURRENT, added in Fortran 2008, is how you make that promise explicitly, and in return the compiler is allowed to vectorize, parallelize, or reorder iterations however it likes.
The problem: sequential semantics by default
Classic Fortran DO loops have strictly defined sequential execution order. That's a feature for correctness, but it ties the optimizer's hands. Even when you, the programmer, know that each iteration of a loop writes to a distinct array element and reads nothing written by other iterations, the compiler often can't prove it — especially once pointers, module variables, or procedure calls get involved.
Historically the escape hatch was OpenMP directives (!$omp parallel do), which work but add a dependency on OpenMP support, clutter the source, and push responsibility for scheduling details onto you. DO CONCURRENT takes a different approach: it's part of the language itself, so any conforming Fortran 2008 compiler understands it, and you're stating a fact about your algorithm rather than issuing threading instructions.
What the construct actually says
The syntax looks like an ordinary loop with a header that lists index variables and optional masks:
do concurrent (i = 1:n, j = 1:m)
a(i,j) = real(i + j)
end doMultiple index triplets are allowed, and you can attach a scalar condition to skip iterations:
do concurrent (k = 1:n, x(k) > 0.0)
y(k) = sqrt(x(k))
end doThe contract you're signing is this: no iteration may read or write a variable that another iteration also writes (loop indices excepted), and no iteration may depend on another having already run. If you break that contract, the behavior is undefined — the compiler is explicitly permitted to run iterations in any order or simultaneously, so a hidden dependence becomes a race condition rather than a deterministic wrong answer.
One subtlety worth knowing: variables you want to be per-iteration temporaries should be declared in a BLOCK construct inside the loop, or the compiler treats them as shared across iterations:
do concurrent (i = 1:n)
block
real :: tmp
tmp = b(i) * scale
a(i) = tmp + c(i)
end block
end doA worked example you can check
Consider initializing a 2-D field and computing a row-wise reduction where each row is independent:
program dc_demo
implicit none
integer, parameter :: n = 2000, m = 2000
real, allocatable :: a(:,:), rowsum(:)
integer :: i, j
allocate(a(n,m), rowsum(n))
do concurrent (i = 1:n, j = 1:m)
a(i,j) = sin(real(i)) * cos(real(j))
end do
do concurrent (i = 1:n)
rowsum(i) = sum(a(i,:))
end do
print '(A, ES16.8)', 'checksum: ', sum(rowsum)
end program dc_demoEach iteration of both loops touches only its own slice of data, so the contract holds. Compile it with a Fortran 2008-or-later compiler — gfortran 6+, Intel ifort/ifx, or NAG 6.0+ all qualify:
gfortran -O3 -fopenmp -o dc_demo dc_demo.f90
./dc_demoRun this in a terminal where gfortran is installed; no special permissions are needed. The -fopenmp flag lets gfortran actually spread DO CONCURRENT iterations across threads (without it you still get vectorization benefits from -O3). With Intel compilers the equivalent is -qopenmp (ifx) plus optimization flags. The checksum printed is deterministic for a given n/m and math library, so you can diff it against the same program rewritten with plain DO loops — the results should agree to within floating-point reduction-order noise, and exactly for the integer-free initialization loop.
Verification, because the compiler won't save you
Here's the uncomfortable part: if you violate the independence rule, most compilers won't warn you. They'll happily produce a program that races. So verification is on you:
- Reference comparison. Keep (or temporarily write) the sequential version and compare outputs. This catches logic errors but not latent races.
- Sanitizers. Build with thread-checking tools — Intel Inspector, or ThreadSanitizer where your toolchain supports it — and run on multi-core hardware. A clean report is real evidence the loop body is dependence-free.
- Performance sanity check. Time the loop with and without parallel flags. A measurable speedup on multi-core hardware indicates the compiler actually exploited the construct; no change usually means it vectorized only, or decided the loop wasn't worth parallelizing.
The trade-offs
DO CONCURRENT is not a free lunch. First, the independence requirement is strict: accumulations like total = total + a(i) are illegal inside the construct (use SUM or a reduction outside the loop instead). Second, parallel execution is permitted, not guaranteed — a compiler may ignore the hint entirely, so don't design around an assumed thread count. Third, portability: pre-2008 compilers reject the syntax outright, so libraries that still support ancient toolchains need conditional compilation or a fallback plain-DO path.
There's also a readability argument in both directions. Fans note that DO CONCURRENT documents intent better than OpenMP pragmas; skeptics note that a wrong promise is worse than no promise.
Where to start
Pick one hot, embarrassingly simple loop in your code — array initialization, element-wise transforms, independent row/column operations — convert it to DO CONCURRENT, and verify with a checksum comparison plus a sanitizer run. If the results match and the timing improves under parallel flags, you've found the pattern to repeat. If the loop turns out to have a hidden cross-iteration dependence, you've learned something valuable about your algorithm before it became a race in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.