Here's a hacked together version of how I would do it - it's essentially a version of what @veryreverie suggests: generate a set of thread private lists and then concatenate them. Note
- I have assumed you don't care what order the elements are listed. If you do you now have an ordering an as such an inherently non-parallel problem, which will be somewhat more difficult to solve
- A program which can't test its results is pointless - so my program checks the 2, 3 and 4 thread results against the single threaded ones. Note because it is Friday evening and I am feeling very lazy this checking, though vital, is done horribly inefficiently, and in fact for big cases takes much longer than the calculation itself!
Anyway here is the code, how it is compiled, and some example results on my quad core laptop:
ijb@ijb-Latitude-5410:~/work/stack$ cat listing.f90
Program listing
Use, Intrinsic :: iso_fortran_env, Only : wp => real64, li => int64
Implicit None
Type element_type
Integer :: i, j
Real( wp ) :: Hij
End Type element_type
Type( element_type ), Dimension( : ), Allocatable :: list_of_elements_serial
Type( element_type ), Dimension( : ), Allocatable :: list_of_elements
Integer :: n
Integer :: nth
Integer( li ) :: start, finish, rate
Logical :: worked
Write( *, * ) 'n ?'
Read ( *, * ) n
nth = 1
Call system_clock( start, rate )
! On a Single thread generate a reference list to check against
Call generate_list( n, nth, list_of_elements_serial )
Call system_clock( finish, rate )
Write( *, * ) 'time on ', 1, ' threads = ', Real( finish - start, wp ) / rate, Size( list_of_elements_serial )
! On 2, 3, 4 generate the lists, compare performance, check the results are correct
Do nth = 2, 4
Call system_clock( start, rate )
Call generate_list( n, nth, list_of_elements )
Call system_clock( finish, rate )
Write( *, * ) 'time on ', nth, ' threads = ', Real( finish - start, wp ) / rate, Size( list_of_elements )
Call checkit( list_of_elements_serial, list_of_elements, worked )
Write( *, '( "Checking ... ")', Advance = 'No' )
If( .Not. worked ) Then
Write( *, * ) 'Failed on ', nth, Size( list_of_elements )
Else
Write( *, * ) 'Worked'
End If
End Do
Contains
Subroutine generate_list( n, nth, list_of_elements )
! Generate a list of the non-zero elements
Use, Intrinsic :: iso_fortran_env, Only : wp => real64, li => int64
Use omp_lib, Only : omp_get_thread_num
Implicit None
Integer , Intent( In ) :: n ! Size of matrix
Integer , Intent( In ) :: nth ! number of threads
Type( element_type ), Dimension( : ), Allocatable, Intent( Out ) :: list_of_elements ! The list of elements
Real( wp ), Parameter :: tol = 1.0e-16_wp
Integer, Parameter :: n_chunk = 16384
Type( element_type ), Dimension( : ), Allocatable :: private_list
Type( element_type ), Dimension( : ), Allocatable :: temp_list
Real( wp ) :: v
Integer, Dimension( : ), Allocatable :: counts
Integer :: private_count
Integer :: my_start
Integer :: i, j
Interface
Pure Function func( n, i, j ) Result( v )
Use, Intrinsic :: iso_fortran_env, Only : wp => real64, li => int64
Real( wp ) :: v
Integer, Intent( In ) :: n
Integer, Intent( In ) :: i
Integer, Intent( In ) :: j
End Function func
End Interface
!$omp parallel num_threads( nth ) default( none ) &
!$omp private( private_count, private_list, temp_list, my_start, v, i, j ) &
!$omp shared( n, nth, counts, list_of_elements )
! Generate a subset of the elements local to this thread
Allocate( private_list( 1:n_chunk ) )
private_count = 0
!$omp do
Do i = 1, n
Do j = 1, n
v = func( n, i, j )
If( Abs( v ) > tol ) Then
private_count = private_count + 1
If( private_count > Ubound( private_list, Dim = 1 ) ) Then
Allocate( temp_list( 1:Ubound( private_list, Dim = 1 ) + n_chunk ) )
temp_list( 1:Ubound( private_list, Dim = 1 ) ) = private_list
Call move_alloc( temp_list, private_list )
End If
private_list( private_count )%i = i
private_list( private_count )%j = j
private_list( private_count )%Hij = v
End If
End Do
End Do
! Concatenate the private lists into one shared list
!$omp single
Allocate( counts( 0:nth - 1 ) )
!$omp end single
counts( omp_get_thread_num() ) = private_count
!$omp barrier
!$omp single
Allocate( list_of_elements( 1:Sum( counts ) ) )
!$omp end single
my_start = Sum( counts( 0:omp_get_thread_num() - 1 ) ) + 1
list_of_elements( my_start:my_start + private_count - 1 ) = private_list( 1:private_count )
!$omp end parallel
End Subroutine generate_list
Pure Subroutine checkit( list_ref, list, worked )
! Check whether the given list is just a rearrangement of the reference list
! HORRIBLY inefficient, should really use sorting - can't be bothered.
Implicit None
Type( element_type ), Dimension( : ), Intent( In ) :: list_ref
Type( element_type ), Dimension( : ), Intent( In ) :: list
Logical , Intent( Out ) :: worked
Type( element_type ), Dimension( : ), Allocatable :: temp
Integer :: i, j
worked = .True.
If( Size( list_ref ) /= Size( list ) ) Then
worked = .False.
End If
Allocate( temp, Source = list )
Do i = 1, Size( list_ref )
Do j = 1, Size( list )
! Search for element i of the reference list in the list being checked
If( list_ref( i )%i == temp( j )%i .And. &
list_ref( i )%j == temp( j )%j .And. &
Abs( list_ref( i )%Hij - temp( j )%Hij ) < 1e-15_wp ) Then
Exit
End If
End Do
If( j == Size( list ) + 1 ) Then
worked = .False.
Return
End If
! Mark it as used already
temp( j )%i = -1
temp( j )%j = -1
temp( j )%Hij = Huge( temp( j )%Hij )
End Do
End Subroutine checkit
End Program listing
Pure Function func( n, i, j ) Result( v )
! silly function for sparse matrix
Use, Intrinsic :: iso_fortran_env, Only : wp => real64, li => int64
Real( wp ) :: v
Integer, Intent( In ) :: n
Integer, Intent( In ) :: i
Integer, Intent( In ) :: j
If( 100 * i < n .And. 100 * j < n ) Then
v = 1.0_wp
Else
v = 0.0_wp
End If
End Function func
ijb@ijb-Latitude-5410:~/work/stack$ gfortran-11 --version
GNU Fortran (GCC) 11.1.0
Copyright © 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
ijb@ijb-Latitude-5410:~/work/stack$ gfortran-11 -std=f2008 -Wall -Wextra -O3 -g -fopenmp listing.f90 -o gen_list
ijb@ijb-Latitude-5410:~/work/stack$ ./gen_list
n ?
10000
time on 1 threads = 6.7302687000000000E-002 9801
time on 2 threads = 2.6817233999999999E-002 9801
Checking ... Worked
time on 3 threads = 1.5919547999999999E-002 9801
Checking ... Worked
time on 4 threads = 1.1952938000000000E-002 9801
Checking ... Worked
ijb@ijb-Latitude-5410:~/work/stack$ ./gen_list
n ?
30000
time on 1 threads = 0.44568265400000001 89401
time on 2 threads = 0.21186449299999999 89401
Checking ... Worked
time on 3 threads = 0.14133034500000000 89401
Checking ... Worked
time on 4 threads = 0.12390519100000000 89401
Checking ... Worked
ijb@ijb-Latitude-5410:~/work/stack$ ./gen_list
n ?
60000
time on 1 threads = 1.7274770189999999 358801
time on 2 threads = 0.85456061200000000 358801
Checking ... Worked
time on 3 threads = 0.57058082499999996 358801
Checking ... Worked
time on 4 threads = 0.42949695500000001 358801
Checking ... Worked
ijb@ijb-Latitude-5410:~/work/stack$