-
Notifications
You must be signed in to change notification settings - Fork 1
/
forbenchmark_default.f90
407 lines (350 loc) · 17.5 KB
/
forbenchmark_default.f90
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
module forbenchmark_default
!! author: Seyed Ali Ghasemi
!! license: BSD 3-Clause License
!! A Fortran module for benchmarking and performance evaluation for non-coarray codes.
!!
use kinds
use fortime, only: timer
implicit none
private
public benchmark
!===============================================================================
type :: mark
!! author: Seyed Ali Ghasemi
!! Derived type for each method being benchmarked.
!!
character(:), allocatable :: method !! Name of the method being benchmarked
character(:), allocatable :: description !! Description of the method being benchmarked
type(timer) :: time !! Timer object to measure elapsed time
real(rk) :: elapsed_time !! Elapsed time for the benchmark
real(rk) :: speedup !! Speedup relative to a reference benchmark
real(rk) :: flops !! Floating-point operations per second
contains
procedure, private :: finalize_mark !! Finalize procedure for mark type
end type mark
!===============================================================================
!===============================================================================
type :: benchmark
!! author: Seyed Ali Ghasemi
!! Derived type for benchmarking and performance evaluation.
!!
type(mark), dimension(:), allocatable :: marks !! Array of marks to store benchmark data
character(:), allocatable :: filename !! Filename for storing benchmark data
integer :: nloops !! Number of loops for each benchmark
integer(ik), dimension(:), allocatable :: argi !! Integer arguments for benchmarks
real(rk), dimension(:), allocatable :: argr !! Real arguments for benchmarks
character(:), allocatable :: timer !! Timer object for measuring time
integer :: imark !! Index of current benchmark mark
contains
procedure :: init !! Initialize the benchmark object
procedure :: start_benchmark !! Start a benchmark
procedure :: stop_benchmark !! Stop a benchmark
procedure, private :: write_benchmark !! Write benchmark data to file
procedure :: finalize !! Finalize the benchmark object
end type benchmark
!===============================================================================
contains
!===============================================================================
elemental impure subroutine init(this, nmarks, title, filename, nloops, timer)
!! author: Seyed Ali Ghasemi
!! Initialize the benchmark object.
!!
use, intrinsic :: iso_fortran_env, only: compiler_version, compiler_options
class(benchmark), intent(inout) :: this !! Benchmark object
integer, intent(in) :: nmarks !! Number of methods to be benchmarked
character(*), intent(in), optional :: title !! Title of the benchmark
character(*), intent(in), optional :: filename !! Filename for storing benchmark data
integer, intent(in), optional :: nloops !! Number of loops for each benchmark (default: 10)
character(*), intent(in), optional :: timer !! Timer object for measuring time (default: wall). The timer options available are 'wall', 'date_and_time', 'cpu', 'omp', and 'mpi'.
integer :: nunit !! Unit number for file access
integer :: iostat !! I/O status
integer :: which_compiler !! Logical variables for compiler detection
character(:), allocatable :: compiler !! Compiler name
if (nmarks <= 0) error stop 'nmarks must be greater than zero.'
allocate(this%marks(nmarks))
compiler =''
which_compiler = index(compiler_version(), 'Intel(R) Fortran Compiler')
if (which_compiler /= 0) compiler = '_ifx'
which_compiler = index(compiler_version(), 'Intel(R) Fortran Intel(R)')
if (which_compiler /= 0) compiler = '_ifort'
which_compiler = index(compiler_version(), 'GCC')
if (which_compiler /= 0) compiler = '_gfortran'
which_compiler = index(compiler_version(), 'nvfortran')
if (which_compiler /= 0) compiler = '_nvfortran'
if (present(filename)) then
this%filename = trim(filename//compiler//'.data')
else
this%filename = 'benchmark'//compiler//'.data'
endif
if (present(nloops)) then
if (nloops <= 0) error stop 'nloops must be greater than zero.'
this%nloops = nloops
else
this%nloops = 10
end if
if (present(timer)) then
select case (trim(timer))
case ('wall')
this%timer = 'wall'
case ('date_and_time')
this%timer = 'date_and_time'
case ('cpu')
this%timer = 'cpu'
case ('omp')
#if defined(USE_OMP)
this%timer = 'omp'
#else
error stop 'Use -DUSE_OMP to enable OpenMP.'
#endif
case ('mpi')
#if defined(USE_MPI)
this%timer = 'mpi'
#else
error stop 'Use -DUSE_MPI to enable MPI.'
#endif
case default
error stop 'timer is not valid. Valid options are: wall, date_and_time, cpu, omp, mpi.'
end select
else
this%timer = 'wall'
end if
inquire(file=this%filename, iostat=iostat)
if (iostat /= 0) then
error stop 'file '//trim(this%filename)//' cannot be accessed.'
end if
open (newunit = nunit, file = this%filename)
write(nunit,'(a)') '-----------------------------------------------------'
write(nunit,'(a)') 'ForBenchmark - https://github.com/gha3mi/forbenchmark'
write(nunit,'(a)') '-----------------------------------------------------'
write(nunit,'(a)') ''
if (present(title)) then
write(nunit,'(a)') trim(title)
else
write(nunit,'(a)') 'ForBenchmark'
endif
write(nunit,'(a)') current_date_and_time()
write(nunit,'(a)') ''
write(nunit,'(a,a)') 'compiler_version: ', compiler_version()
write(nunit,'(a,a)') 'compiler_options: ', compiler_options()
write(nunit,'(a)') ''
write(nunit,'(a)') ''
write(nunit,'(a)') &
&' METHOD |&
& SPEEDUP |&
& TIME |&
& GFLOPS |&
& NLOOPS |&
& ARGI '
close(nunit)
end subroutine init
!===============================================================================
!===============================================================================
impure subroutine start_benchmark(this, imark, method, description, argi, argr)
!! author: Seyed Ali Ghasemi
!! Start a specific benchmark
!!
use face
class(benchmark), intent(inout) :: this !! Benchmark object
integer, intent(in) :: imark !! Index of the current method
character(*), intent(in) :: method !! Name of the method being benchmarked
integer(ik), dimension(:), intent(in), optional :: argi !! Integer arguments for the benchmark (optional)
real(rk), dimension(:), intent(in), optional :: argr !! Real arguments for the benchmark (optional)
character(*), intent(in), optional :: description !! Description of the method being benchmarked (optional)
if (imark <= 0 .or. imark > size(this%marks)) error stop 'imark is out of range.'
this%imark = imark
this%marks(this%imark)%description = description
this%marks(this%imark)%method = method
if (present(argi)) then
this%argi = argi
else
if(.not. allocated(this%argi)) allocate(this%argi(0))
endif
if (present(argr)) then
this%argr = argr
else
if(.not. allocated(this%argr)) allocate(this%argr(0))
endif
if (present(description) .and. present(argi)) then
print'(a,a," ",a,*(g0,1x))',&
colorize('Meth.: '//this%marks(this%imark)%method, color_fg='green',style='bold_on'),&
colorize('; Des.: '//this%marks(this%imark)%description, color_fg='green_intense'),&
'; Argi.:',&
this%argi
elseif (present(description) .and. .not. present(argi)) then
print'(a,a," ",a)',&
colorize('Meth.: '//this%marks(this%imark)%method, color_fg='green',style='bold_on'),&
colorize('; Des.: '//this%marks(this%imark)%description, color_fg='green_intense')
elseif (.not. present(description) .and. present(argi)) then
print'(a,a,*(g0,1x))',&
colorize('Meth.: '//this%marks(this%imark)%method, color_fg='green',style='bold_on'),&
'; Argi.:',&
this%argi
else
print'(a)', colorize('Meth.: '//this%marks(this%imark)%method, color_fg='green',style='bold_on')
end if
select case (trim(this%timer))
case ('wall')
call this%marks(this%imark)%time%timer_start()
case ('date_and_time')
call this%marks(this%imark)%time%dtimer_start()
case ('cpu')
call this%marks(this%imark)%time%ctimer_start()
case ('omp')
#if defined(USE_OMP)
call this%marks(this%imark)%time%otimer_start()
#else
error stop 'Use -DUSE_OMP to enable OpenMP.'
#endif
case ('mpi')
#if defined(USE_MPI)
call this%marks(this%imark)%time%mtimer_start()
#else
error stop 'Use -DUSE_MPI to enable MPI.'
#endif
end select
end subroutine start_benchmark
!===============================================================================
!===============================================================================
impure subroutine stop_benchmark(this, flops)
!! author: Seyed Ali Ghasemi
!! Stops the currently active benchmark, calculates performance metrics, and writes the results to the file and terminal.
!!
interface
impure function Fun(argi, argr)
import rk, ik
integer(ik), dimension(:), intent(in), optional :: argi
real(rk), dimension(:), intent(in), optional :: argr
real(rk) :: Fun
end function Fun
end interface
procedure(Fun), optional :: flops !! Function to calculate Floating Point Operations Per Second (optional)
class(benchmark), intent(inout) :: this !! Benchmark object
select case (trim(this%timer))
case ('wall')
call this%marks(this%imark)%time%timer_stop(message=' Elapsed time :',nloops=this%nloops)
this%marks(this%imark)%elapsed_time = this%marks(this%imark)%time%elapsed_time
case ('date_and_time')
call this%marks(this%imark)%time%dtimer_stop(message=' Elapsed time :',nloops=this%nloops)
this%marks(this%imark)%elapsed_time = this%marks(this%imark)%time%elapsed_dtime
case ('cpu')
call this%marks(this%imark)%time%ctimer_stop(message=' Elapsed time :',nloops=this%nloops)
this%marks(this%imark)%elapsed_time = this%marks(this%imark)%time%cpu_time
case ('omp')
#if defined(USE_OMP)
call this%marks(this%imark)%time%otimer_stop(message=' Elapsed time :',nloops=this%nloops)
this%marks(this%imark)%elapsed_time = this%marks(this%imark)%time%omp_time
#else
error stop 'Use -DUSE_OMP to enable OpenMP.'
#endif
case ('mpi')
#if defined(USE_MPI)
call this%marks(this%imark)%time%mtimer_stop(message=' Elapsed time :',nloops=this%nloops)
this%marks(this%imark)%elapsed_time = this%marks(this%imark)%time%mpi_time
#else
error stop 'Use -DUSE_MPI to enable MPI.'
#endif
end select
if (this%marks(this%imark)%elapsed_time <= epsilon(0.0_rk)) error stop 'Elapsed time is too small'
this%marks(this%imark)%speedup = this%marks(this%imark)%elapsed_time/this%marks(1)%elapsed_time
if (present(flops)) then
print'(a,f7.3,a)', ' Speedup :', this%marks(this%imark)%speedup,' [-]'
this%marks(this%imark)%flops = flops(this%argi,this%argr)/this%marks(this%imark)%elapsed_time
print'(a,f7.3,a)', ' Performance :', this%marks(this%imark)%flops,' [GFLOPS]'
else
this%marks(this%imark)%flops = 0.0_rk
endif
print'(a)', ''
call this%write_benchmark()
end subroutine stop_benchmark
!===============================================================================
!===============================================================================
impure subroutine write_benchmark(this)
!! author: Seyed Ali Ghasemi
!! Writes the benchmark data to a specified file, including method, speedup, elapsed time, flops, and other details.
!!
class(benchmark), intent(inout) :: this !! Benchmark object
integer :: nunit !! Unit number for file access
character(len=65) :: fmt !! Format for write
logical :: exist !! Logical variable for file existence
integer :: iostat !! I/O status
integer :: lm
inquire(file=this%filename, exist=exist, iostat=iostat)
if (iostat /= 0 .or. .not. exist) then
error stop 'file '//trim(this%filename)//' does not exist or cannot be accessed.'
end if
open (newunit = nunit, file = this%filename, access = 'append')
lm = 20-len_trim(this%marks(this%imark)%method)
write(fmt,'(a,g0,a)') '(a,',lm,'x,3x,F12.6,3x,E20.14,3x,E20.14,3x,g8.0,3x,*(g20.0,3x))'
write(nunit,fmt) &
this%marks(this%imark)%method,&
this%marks(this%imark)%speedup,&
this%marks(this%imark)%elapsed_time,&
this%marks(this%imark)%flops,&
this%nloops,&
this%argi
close(nunit)
end subroutine write_benchmark
!===============================================================================
!===============================================================================
elemental pure subroutine finalize_mark(this)
!! author: Seyed Ali Ghasemi
!! Finalizes the mark object by deallocating allocated memory for method and description.
!!
class(mark), intent(inout) :: this !! Mark object to be finalized
if (allocated(this%method)) deallocate(this%method)
if (allocated(this%description)) deallocate(this%description)
end subroutine finalize_mark
!===============================================================================
!===============================================================================
elemental impure subroutine finalize(this)
!! author: Seyed Ali Ghasemi
!! Finalizes the benchmark object by deallocating memory and performs necessary cleanup.
!!
class(benchmark), intent(inout) :: this !! Benchmark object to be finalized
integer :: nunit !! Unit number for file access
logical :: exist !! Logical variable for file existence
integer :: iostat !! I/O status
inquire(file=this%filename, exist=exist, iostat=iostat)
if (iostat /= 0 .or. .not. exist) then
error stop 'file '//trim(this%filename)//' does not exist or cannot be accessed.'
end if
open (newunit = nunit, file = this%filename, access = 'append')
write(nunit,'(a)') 'end of benchmark'
close(nunit)
call this%marks(:)%finalize_mark()
if (allocated(this%filename)) deallocate(this%filename)
if (allocated(this%argi)) deallocate(this%argi)
if (allocated(this%argr)) deallocate(this%argr)
print'(a)', 'end of benchmark'
end subroutine finalize
!===============================================================================
!===============================================================================
impure function current_date_and_time() result(datetime)
!! author: Seyed Ali Ghasemi
!! Retrieves the current date and time and returns it as a string
!! It utilizes the intrinsic `date_and_time` function to obtain system time information.
!! A string containing the current date and time in the format "YYYY.MM.DD - HH:MM:SS".
!!
character(21) :: datetime !! Character containing the current date and time
character(10) :: date !! Character containing the current date
character(8) :: time !! Character containing the current time
integer :: values(8) !! Array containing the current date and time values
character(4) :: year !! Current year
character(2) :: month !! Current month
character(2) :: day !! Current day
character(2) :: hour !! Current hour
character(2) :: minute !! Current minute
character(2) :: second !! Current second
call date_and_time(values=values)
write(year,'(i4)') values(1)
write(month,'(i2)') values(2)
write(day,'(i2)') values(3)
write(hour,'(i2)') values(5)
write(minute,'(i2)') values(6)
write(second,'(i2)') values(7)
date=year//'.'//month//'.'//day
time=hour//':'//minute//':'//second
datetime = date//' - '//time
end function current_date_and_time
!===============================================================================
end module forbenchmark_default