180 Introduction to High Performance Computing for Scientists and Engineers
12 IND = A(i)
13 S(ID,IND) = S(ID,IND) + 1
14 enddo
15 !$OMP END DO NOWAIT
16 ! calculate complete histogram
17 !$OMP CRITICAL
18 do j=1,8
19 S(0,j) = S(0,j) + S(ID,j)
20 enddo
21 !$OMP END CRITICAL
22 !$OMP END PARALLEL
The loop starting at line 18 collects the partial results of all threads. Although this is a
valid OpenMP program, it will not run faster but much more slowly when using four
threads instead of one. The reason is that the two-dimensional array S contains all the
histogram data from all threads. With four threads these are 160 bytes, corresponding
to two or three cache lines on most processors. On each histogram update to S in
line 13, the writing CPU must gain exclusive ownership of one of those cache lines;
almost every write leads to a cache miss and subsequent coherence traffic because it
is likely that the needed cache line resides in another processor’s cache, in modified
state. Compared to the serial case where S fits into the cache of a single CPU, this
will result in disastrous performance.
One should add that false sharing can be eliminated in simple cases by the stan-
dard register optimizations of the compiler. If the crucial update operation can be
performed to a register whose contents are only written out at the end of the loop, no
write misses turn up. This is not possible in the above example, however, because of
the computed second index to S in line 13.
Getting rid of false sharing by manual optimizationis often a simpletask once the
problem has been identified. A standard technique is array padding, i.e., insertion of
a suitable amount of space between memory locations that get updated by different
threads. In the histogram example above, allocating S in line 6 as S(0:NT
*
CL,8),
with CL being the cache line size in integers, will reserve an exclusive cache line
for each thread. Of course, the first index to S will have to be multiplied by CL
everywhere else in the program (transposing S will save some memory, but the main
principle stays the same).
An even more painless solution exists in the form of data privatization (see also
Section 7.2.3 above): On entry to the parallel region, each thread gets its own local
copy of the histogram array in its own stack space. It is very unlikely that those
different instances will occupy the same cache line, so false sharing is not a problem.
Moreover, the code is simplified and made equivalent to the purely serial version by
using the REDUCTION clause:
1 integer, dimension(8) :: S
2 integer :: IND
3 S=0
4 !$OMP PARALLEL DO PRIVATE(IND) REDUCTION(+:S)
5 do i=1,N
6 IND = A(i)
7 S(IND) = S(IND) + 1