问题描述:

I have an array assignment that I formulated using forall. The indices to one array have to be computed from the loop variables, so I tried to use temporary variables in a way analogous to ii this simple example:

program test

integer :: i, ii, a(10), b(20)

a=(/(i, i=1,10)/)

forall (i=1:20)

ii = (i+1)/2

b(i) = a(ii)

end forall

print '(20I3)', b

end program test

But gfortran warns me that “The FORALL with index 'i' is not used on the left side of the assignment at (1) and so might cause multiple assignment to this object”, and indeed the output is not what I wanted:

10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10

(ifort even throws an error.) I realize I could just repeat the definition of ii every time it appears, but that can get tedious in real-life cases.

Q1: Is there a way to do this sort of thing correctly using forall?


Now, as you can read in some other questions, forall no longer seems to be recommended, and a straight do may even be faster.

However, my use of forall in this instance was as much about readability as about speed. In my real program, I have something like forall (i=1:10, j=1:10, i<=j), and I like the way the forall header lets me specify this without nested loops.

I also learned about do concurrent in those questions, and that seems to solve the problem, i.e. replacing forall above with do concurrent, the output becomes:

1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10

and I still get to use a forall header.

Q2: Is this use of a local variable, which does not work in forall, guaranteed by the standard to work in do concurrent? (Rather than it being a fluke that it worked in gfortran.)

Unfortunately, for now I cannot use do concurrent anyway, because slightly older ifort and gfortran versions do not support it.

网友答案:

As Mark points out, defining ii inside the forall construct is unnecessary as you can use a((i+1)/2) in your definition. As yet another alternative, if you are completely wedded to using the forall, you can use pure function (or an elemental function) to set the indices:

program forall_test
   ...
  forall(i=1:20)
     b(i) = a(set(i))
  end forall 

contains
  pure function set(i)
    integer, intent(in) :: i
    integer :: set
    set = (i+1)/2
  end function set
end program

Using this with a=(/1, 2, 3, 4, 5, 6, 7, 8, 9, 10/), I get as output

1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10
网友答案:

For the case in your question I don't see the need for the scalar ii at all, you could replace

  forall (i=1:20)
     ii = (i+1)/2

     b(i) = a(ii)
  end forall

with

  forall (i=1:20)
     b(i) = a((i+1)/2)
  end forall

which produces the result you want, in this case. As to your questions:

A1: Yes, as illustrated.

A2: do concurrent is a standard feature in Fortran 2008 so it is guaranteed to work; guaranteed if you have an up-to-date compiler and that compiler has been correctly implemented. So far I've only used do concurrent with a recent edition of the Intel Fortran compiler and have found no non-standard behaviour.

After Vladimir F's comment:

Yes, the rewritten forall construct may require a temporary array behind the scenes but I believe that what I wrote is syntactically correct and semantically equivalent to OP's original code. As to the legality of a do concurrent construct, the following

do concurrent (i=1:20)
   b(i) = a((i+1)/2)
end do

compiles and executes just fine. Again, I think this is standard-conforming code and behaviour.

网友答案:

You seem to be using forall where I would naturally use a do loop:

do i=1,20
  ii=(i+1)/2
  b(i)=a(ii)
end do

The forall statement is especially useful for conditional one-liners where you want to apply a mask to a whole-array assignment, but even then I personally prefer do loops.

Your example of forall (i=1:10, j=1:10, i<=10) also doesn't make much sense, as the final expression doesn't mask any values.

The idea behind forall and other whole-array assignments is that the compiler would know how to optimize the whole thing better, but in practice (well, at least since last I checked personally), it usually leads to slower code.

EDIT:

just wanted to mention that it's probably better to directly assign your computed integer to the array value in b, instead of fetching the same thing from memory again through the use of a:

do i=1,20
  b(i)=(i+1)/2
end do

but I hope this is just because the example is over-simplified? :)

相关阅读:
Top