On Saturday, October 15, 2016 at 4:03:58 PM UTC-4, Stefano Zaghi wrote:
> ..
>
> My test indicates that all the three branching flow are comparable and goto is the worse.
>
> I have tried to address what I think your test missed:
>
> 1. I provide a complete program;
> 2. I tried to mimic a varying work-load into each branch;
> 3. I tried to enter into each branch randomically (pseudo-random at least);
> 4. I tried to measure only the branching time;
>
> .. Goto is to be avoided for much important reasons than the (supposed) higher speed of execution.
>
> ..
Stefano,
Great work, you should create a Fortran MYTHBUSTERS collection on GitHub!!
https://en.wikipedia.org/wiki/MythBusters
Fyi, I was thinking somewhat along the same lines and ran a test around the SAME time as your post above - I list the details below which you can review and discard/include in any way you see fit. My read is also the same, there is hardly any discernible difference in CPU performance between the two approaches, but coders may again want to be reminded,
a) SELECT CASE helps in writing clear code that should be easier to understand and maintain,
b) computed GOTO is obsolescent in the Fortran standard, and
c) SELECT CASE works with integer, logical, AND *CHARACTER* scalar expressions whereas computed GOTO only uses scalar numeric expressions that may be converted to integer type.
Readers need to keep point (c) in mind, especially with respect to the original post that showed constructs such as << CASE ('keyword1') >>. A coder would then need to take some extra action - use of some integer tables, perhaps - to utilize computed GOTO statements.
-- begin case --
module mykinds_m
use, intrinsic :: iso_fortran_env, only : I4 => int32, WP => real64
implicit none
real(WP), parameter :: ZERO = 0.0_wp
end module mykinds_m
module procs_m
use mykinds_m, only : WP, ZERO
implicit none
contains
subroutine s1( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s1: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s1
subroutine s2( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s2: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s2
subroutine s3( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s3: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s3
subroutine s4( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s4: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s4
subroutine s5( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s5: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s5
subroutine s6( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s6: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s6
subroutine s7( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s7: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s7
subroutine s8( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s8: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s8
subroutine s9( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s9: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s9
subroutine s10( n, r )
!.. Argument list
integer, intent(in) :: n
real(WP), intent(inout) :: r
!.. Local variables
integer :: v_size
integer :: istat
real(WP), allocatable :: v(:)
v_size = 2**( min(n,10) )
allocate( v(v_size), stat=istat )
if ( istat /= 0 ) then
print *, "s10: allocation of v failed: v_size, stat = ", v_size, istat
stop
end if
call random_number( v )
r = norm2( v )
return
end subroutine s10
end module procs_m
module m
use procs_m
implicit none
private
character(len=*), parameter, public :: keywords(*) = [ character(len=3) :: "s1", "s2", "s3", &
"s4", "s5", "s6", "s7", "s8", "s9", "s10" ]
integer, parameter, public :: ikeys(*) = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
public :: s_slc
public :: s_cgt
contains
subroutine s_slc( keyword, n, r )
character(len=*), intent(in) :: keyword
integer, intent(in) :: n
real(WP), intent(inout) :: r
select case ( keyword )
case ( keywords(1) )
call s1( n, r )
case ( keywords(2) )
call s2( n, r)
case ( keywords(3) )
call s3( n, r)
case ( keywords(4) )
call s4( n, r)
case ( keywords(5) )
call s5( n, r)
case ( keywords(6) )
call s6( n, r)
case ( keywords(7) )
call s7( n, r)
case ( keywords(8) )
call s8( n, r)
case ( keywords(9) )
call s9( n, r)
case ( keywords(10) )
call s10( n, r)
case default
print *, "s: invarid keyword of ", keyword
return
end select
return
end subroutine s_slc
subroutine s_cgt( ikey, n, r )
integer, intent(in) :: ikey
integer, intent(in) :: n
real(WP), intent(inout) :: r
goto (1,2,3,4,5,6,7,8,9,10), ikey
1 continue
call s1( n, r )
go to 99
2 continue
call s2( n, r )
go to 99
3 continue
call s3( n, r )
go to 99
4 continue
call s4( n, r )
go to 99
5 continue
call s5( n, r )
go to 99
6 continue
call s6( n, r )
go to 99
7 continue
call s8( n, r )
go to 99
8 continue
call s8( n, r )
go to 99
9 continue
call s9( n, r )
go to 99
10 continue
call s10( n, r )
go to 99 ! wonder if compiler optimization eliminates this
99 continue
return
end subroutine s_cgt
end module m
program p
use mykinds_m, only : I4, WP, ZERO
use m, only : s_slc, s_cgt, keywords
implicit none
!..
integer, parameter :: MAXREPEAT = 10
integer, parameter :: MAXTRIAL = 2**10
integer :: Idx(MAXTRIAL)
integer :: Counter
integer :: j
real(WP) :: Start_Time = ZERO
real(WP) :: End_Time = ZERO
real(WP) :: Ave_Time = ZERO
real(WP) :: CpuTimes_SLC(MAXREPEAT)
real(WP) :: CpuTimes_CGT(MAXREPEAT)
real(WP) :: r(MAXTRIAL)
real(WP) :: x_norm
character(len=*), parameter :: FMT_CPU = "(a, t40, g0, a)"
print *, "Mythbuster #1: SELECT CASE vs COMPUTED GOTO" // new_line("")
CpuTimes_SLC = ZERO
CpuTimes_CGT = ZERO
print *, "SELECT CASE:"
Loop_Repeat_Slc: do Counter = 1, MAXREPEAT
print *, " Trial ", Counter
call random_number(r)
Idx = int( r*10.0_wp, kind=kind(Idx) ) + 1
!..
call my_cpu_time(Start_Time)
do j = 1, MAXTRIAL
call s_slc( keywords( Idx(j) ), Idx(j), x_norm )
end do
call my_cpu_time(End_Time)
CpuTimes_SLC(Counter) = (End_Time - Start_Time)
write(*, fmt=FMT_CPU) " CPU Time: ", CpuTimes_SLC(Counter), " seconds."
end do Loop_Repeat_Slc
print *, "COMPUTED GOTO:"
Loop_Repeat_Cgt: do Counter = 1, MAXREPEAT
print *, " Trial ", Counter
call random_number(r)
Idx = int( r*10.0_wp, kind=kind(Idx) ) + 1
!..
call my_cpu_time(Start_Time)
do j = 1, MAXTRIAL
call s_cgt( Idx(j), Idx(j), x_norm )
end do
call my_cpu_time(End_Time)
CpuTimes_CGT(Counter) = (End_Time - Start_Time)
write(*, fmt=FMT_CPU) " CPU Time: ", CpuTimes_CGT(Counter), " seconds."
end do Loop_Repeat_Cgt
!.. Average CPU time: exclude highest and lowest values
Ave_Time = sum(CpuTimes_SLC)
Ave_Time = Ave_Time - maxval(CpuTimes_SLC) - minval(CpuTimes_SLC)
Ave_Time = Ave_Time/real(MAXREPEAT-2, kind=WP)
write(*, fmt=FMT_CPU) "SELECT CASE: Average CPU Time ", Ave_Time," seconds."
!.. Average CPU time: exclude highest and lowest values
Ave_Time = sum(CpuTimes_CGT)
Ave_Time = Ave_Time - maxval(CpuTimes_CGT) - minval(CpuTimes_CGT)
Ave_Time = Ave_Time/real(MAXREPEAT-2, kind=WP)
write(*, fmt=FMT_CPU) "COMPUTED GOTO: Average CPU Time ", Ave_Time," seconds."
!..
stop
contains
subroutine my_cpu_time( time )
!.. Argument list
real(WP), intent(inout) :: time
!.. Local variables
integer(I4) :: tick
integer(I4) :: rate
call system_clock (tick, rate)
time = real(tick, kind=kind(time) ) / real(rate, kind=kind(time) )
return
end subroutine my_cpu_time
end program p
-- end case --
Upon execution with Intel Fortran with /O2 on a Windows 7 laptop, Intel i5 CPU 2.7 GHz, 8 GB machine,
-- begin results --
Mythbuster #1: SELECT CASE vs COMPUTED GOTO
SELECT CASE:
Trial 1
CPU Time: .9999999892897904E-03 seconds.
Trial 2
CPU Time: .1999999978579581E-02 seconds.
Trial 3
CPU Time: .9999999892897904E-03 seconds.
Trial 4
CPU Time: .1999999978579581E-02 seconds.
Trial 5
CPU Time: .2000000007683411E-02 seconds.
Trial 6
CPU Time: .2000000007683411E-02 seconds.
Trial 7
CPU Time: .1000000018393621E-02 seconds.
Trial 8
CPU Time: .9999999892897904E-03 seconds.
Trial 9
CPU Time: .1999999978579581E-02 seconds.
Trial 10
CPU Time: .2000000007683411E-02 seconds.
COMPUTED GOTO:
Trial 1
CPU Time: .1000000018393621E-02 seconds.
Trial 2
CPU Time: .2000000007683411E-02 seconds.
Trial 3
CPU Time: .2000000007683411E-02 seconds.
Trial 4
CPU Time: .1000000018393621E-02 seconds.
Trial 5
CPU Time: .9999999892897904E-03 seconds.
Trial 6
CPU Time: .1000000018393621E-02 seconds.
Trial 7
CPU Time: .2000000007683411E-02 seconds.
Trial 8
CPU Time: .1999999978579581E-02 seconds.
Trial 9
CPU Time: .2000000007683411E-02 seconds.
Trial 10
CPU Time: .2000000007683411E-02 seconds.
SELECT CASE: Average CPU Time .1624999993509846E-02 seconds.
COMPUTED GOTO: Average CPU Time .1625000008061761E-02 seconds.
-- end results --
Basically all that any CPU time measurement indicates is some statistical mean of the keyword-processor subroutine.