To start it off I submit this skeleton with five versions and timing built in for starters
click for LONG example
module M_case
use,intrinsic :: iso_fortran_env, only : int8, int16, int32, int64
implicit none
private
character(len=26), parameter, private :: low = "abcdefghijklmnopqrstuvwxyz"
character(len=26), parameter, private :: high = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
integer(kind=int8), parameter :: ascii_diff = abs(iachar('A',kind=int8) - iachar('a',kind=int8))
integer :: i
character(len=1),save,private :: convtableL(0:255) = [(char(i),i=0,255)]
character(len=1),save,private :: convtableU(0:255) = [(char(i),i=0,255)]
logical, save :: first = .true.
public :: upper1,lower1 ! assumes ASCII collating sequence
public :: upper2,lower2 ! any one-byte collating sequence, (a)
public :: upper3,lower3 ! any one-byte collating sequence, (a)
public :: upper4,lower4 ! assumes ASCII collating sequence
public :: to_upper, to_lower ! sssumes ASCII collating sequence
! (a) avoids issues with EBCDIC not having A-Z as contigious letters
! subroutines are potentially much faster
contains
function upper1(str) result(translated)
character(*), intent(in) :: str
integer :: i
character(len=len(str)) :: translated
translated=str
do i = 1, len(str)
select case(str(i:i))
case("a":"z")
translated(i:i) = achar(iachar(str(i:i))-ascii_diff)
end select
enddo
end function upper1
function lower1(str) result(translated)
character(*), intent(in) :: str
integer :: i
character(len=len(str)) :: translated
translated=str
do i = 1, len(str)
select case(str(i:i))
case("A":"Z")
translated(i:i) = achar(iachar(str(i:i))+ascii_diff)
end select
enddo
end function lower1
function upper2(str) result(translated)
character(len=*),intent(in) :: str
integer :: i,c
character(len=len(str)) :: translated
translated=str
do i = 1,len(str)
c = index(low,str(i:i))
if (c.gt.0) translated(i:i) = high(c:c)
end do
end
function lower2(str) result(translated)
character(len=*),intent(in) :: str
integer :: i,c
character(len=len(str)) :: translated
translated=str
do i = 1,len(str)
c = index(high,str(i:i))
if (c.gt.0) translated(i:i) = low(c:c)
end do
end function lower2
subroutine loadtable()
integer :: i
do i=1,len(low)
convtableL(iachar(low(i:i))) = char(iachar(high(i:i)))
convtableU(iachar(high(i:i))) = char(iachar(low(i:i)))
enddo
first = .false.
end subroutine loadtable
function upper3(str) result(translated)
character(len=*),intent(in) :: str
character(len=len(str)) :: translated
integer :: i
if(first) call loadtable()
translated = str
do i=1,len_trim(str)
translated(i:i) = convtableL(iachar(str(i:i)))
enddo
end function upper3
function lower3(str) result(translated)
character(len=*),intent(in) :: str
character(len=len(str)) :: translated
integer :: i
if(first) call loadtable()
translated = str
do i=1,len_trim(str)
translated(i:i) = convtableU(iachar(str(i:i)))
enddo
end function lower3
pure function upper4(str) result (translated)
character(*), intent(in) :: str ! input string to convert to all uppercase
character(len(str)) :: translated ! output string that contains no miniscule letters
integer :: i ! loop counter
! note using kind=int8 is faster than int32 in gfortran
integer(kind=int8), parameter :: ade_a = iachar('a'), ade_z = iachar('z')
integer(kind=int8) :: ade_char
do concurrent(i=1:len(str)) ! step thru each letter in the string in specified range
ade_char = iachar(str(i:i), int8) ! ASCII Decimal Equivalent
if (ade_char >= ade_a .and. ade_char <= ade_z) ade_char = ade_char - ascii_diff
translated(i:i) = achar(ade_char)
enddo
if(len(str).eq.0)translated = str
end function upper4
pure function lower4(str) result (translated)
character(*), intent(in) :: str
character(len(str)) :: translated
integer :: i
integer(kind=int8), parameter :: ade_a = iachar('A'), ade_z = iachar('Z')
integer(kind=int8) :: ade_char
translated = str
do concurrent (i = 1:len_trim(str))
ade_char = iachar(str(i:i), int8)
if (ade_char >= ade_a .and. ade_char <= ade_z) ade_char = ade_char + ascii_diff
translated(i:i) = achar(ade_char)
enddo
if(len(str).eq.0)translated = str
end function lower4
!> Returns the corresponding lowercase letter, if `c` is an uppercase
!> ASCII character, otherwise `c` itself.
pure function char_to_lower(c) result(t)
character(len=1), intent(in) :: c !! A character.
character(len=1) :: t
integer, parameter :: wp= iachar('a')-iachar('A'), BA=iachar('A'), BZ=iachar('Z')
integer :: k
!Check whether the integer equivalent is between BA=65 and BZ=90
k = ichar(c)
if (k>=BA.and.k<=BZ) k = k + wp
t = char(k)
end function char_to_lower
!> Returns the corresponding uppercase letter, if `c` is a lowercase
!> ASCII character, otherwise `c` itself.
pure function char_to_upper(c) result(t)
character(len=1), intent(in) :: c !! A character.
character(len=1) :: t
integer, parameter :: wp= iachar('a')-iachar('A'), la=iachar('a'), lz=iachar('z')
integer :: k
!Check whether the integer equivalent is between la=97 and lz=122
k = ichar(c)
if (k>=la.and.k<=lz) k = k - wp
t = char(k)
end function char_to_upper
!> Convert character variable to lower case
!> ([Specification](../page/specs/stdlib_ascii.html#to_lower))
!>
!> Version: experimental
pure function to_lower(string) result(lower_string)
character(len=*), intent(in) :: string
character(len=len(string)) :: lower_string
integer :: i
do i = 1, len(string)
lower_string(i:i) = char_to_lower(string(i:i))
end do
end function to_lower
!> Convert character variable to upper case
!> ([Specification](../page/specs/stdlib_ascii.html#to_upper))
!>
!> Version: experimental
pure function to_upper(string) result(upper_string)
character(len=*), intent(in) :: string
character(len=len(string)) :: upper_string
integer :: i
do i = 1, len(string)
upper_string(i:i) = char_to_upper(string(i:i))
end do
end function to_upper
end module M_case
module M_tictoc
use,intrinsic :: iso_fortran_env, only : int32,int64,real32,dp=>real64
use,intrinsic :: iso_fortran_env, only : stdout=>OUTPUT_UNIT
implicit none
private
type timer
real(kind=dp) :: cpu_start
real(kind=dp) :: cpu_end
integer(kind=int64) :: clock_start
integer(kind=int64) :: clock_end
integer :: wall_start(8)
integer :: wall_end(8)
contains
procedure :: tic => clock_tic
procedure :: toc => clock_toc
procedure :: print => clock_print
procedure :: walltime => clock_walltime
procedure :: cputime => clock_cputime
procedure :: dattime => clock_dattime
end type
interface timer
procedure :: clock_new
end interface timer
! type for unix epoch time and julian days
integer,parameter,public :: realtime=dp
public :: timer
public :: say_hello
public :: catstat
character(len=*),parameter :: gen='(*(g0))'
character(len=*),parameter :: all='(*(g0,1x))'
contains
! initialization constructor
type(timer) function clock_new(this)
type(timer),intent(in),optional :: this
call cpu_time(clock_new%cpu_start)
call system_clock(clock_new%clock_start)
call date_and_time(values=clock_new%wall_start)
clock_new%cpu_end= clock_new%cpu_start
clock_new%clock_end= clock_new%clock_start
clock_new%wall_end= clock_new%wall_start
end function clock_new
subroutine clock_tic(this)
class(timer) :: this
call cpu_time(this%cpu_start)
call system_clock(this%clock_start)
call date_and_time(values=this%wall_start)
this%cpu_end = this%cpu_start
this%clock_end = this%clock_start
this%wall_end = this%wall_start
end subroutine clock_tic
subroutine clock_toc(this)
class(timer) :: this
call cpu_time(this%cpu_end)
call system_clock(this%clock_end)
call date_and_time(values=this%wall_end)
end subroutine clock_toc
subroutine clock_print(this,string,lun)
class(timer),intent(in) :: this
character(len=*),intent(in),optional :: string
integer(kind=int32),intent(in),optional :: lun
integer(kind=int32) :: lun_
real(kind=dp) :: elapsed_time
real(kind=realtime) :: elapsed_date_and_time
real(kind=dp) :: cpu_time
character(len=105) :: biggest
integer(kind=int64) :: count_rate
character(len=:),allocatable :: string_
if(present(lun))then
lun_=lun
else
lun_=stdout
endif
elapsed_time = this%walltime()
elapsed_date_and_time = this%dattime()
cpu_time = this%cputime()
string_ = ''; if(present(string)) string_ = string//':'
if(elapsed_date_and_time >= 0)then
write( lun_,'(a,f0.3)') string_//'Elapsed dat (sec) ::',elapsed_date_and_time
else
write( lun_,'(a)') string_//'Elapsed dat (sec) :: N/A'
endif
! try to make a reasonable format for the number of digits of precision
call system_clock(count_rate=count_rate) ! Find the time rate
write(biggest,'("(a,f0.",i0,")")')ceiling(log10(real(count_rate,kind=dp)))
write( lun_,biggest) string_//'Elapsed time (sec) ::',elapsed_time
write( lun_,gen) string_//'CPU time (sec) ::',cpu_time
write( lun_,'(a,1x,f0.2)') string_//'Percentage ::',(cpu_time/elapsed_time)*100
end subroutine clock_print
function clock_walltime(this) result(elapsed_time)
class(timer) :: this
integer(kind=int64) :: count_rate
real(kind=dp) :: elapsed_time
real(kind=dp) :: cpu_time
call system_clock(count_rate=count_rate)
elapsed_time = real(this%clock_end-this%clock_start,kind=dp)/real(count_rate,kind=dp)
end function clock_walltime
function clock_cputime(this) result(cpu_time)
class(timer) :: this
real(kind=dp) :: cpu_time
cpu_time = real(this%cpu_end-this%cpu_start,kind=dp)
end function clock_cputime
function clock_dattime(this) result(cpu_time)
class(timer) :: this
real(kind=dp) :: cpu_time
real(kind=realtime) :: endit,startit
integer :: ierr
call date_to_julian(this%wall_end,endit,ierr)
call date_to_julian(this%wall_start,startit,ierr)
if(ierr == 0)then
cpu_time = real((endit-startit)*86400,kind=dp)
else
cpu_time = -huge(cpu_time)
endif
end function clock_dattime
subroutine date_to_julian(dat,julian,ierr)
! @(#)M_time::date_to_julian(3f): Converts proleptic Gregorian DAT date-time array to Julian Date
! REFERENCE: From Wikipedia, the free encyclopedia 2015-12-19
! correction for time zone should or should not be included?
integer,intent(in) :: dat(8)! array like returned by DATE_AND_TIME(3f)
real(kind=realtime),intent(out) :: julian
integer,intent(out) :: ierr ! 0 =successful, -1=bad year, -4=bad date 29 Feb, non leap-year, -6 negative value -9 bad input
integer :: a , y , m , jdn
integer :: utc
utc=dat(4)*60
julian = -huge(99999) ! this is the date if an error occurs and IERR is < 0
if(any(dat == -huge(dat)))then
ierr=-9
return
endif
associate&
&(year=>dat(1),month=>dat(2),day=>dat(3),utc=>utc,hour=>dat(5),minute=>dat(6),second=>dat(7)-utc+dat(8)/1000.0d0)
if ( year==0 .or. year<-4713 ) then
ierr = -1
else
ierr=0
! You must compute first the number of years (Y) and months (M) since March 1st -4800 (March 1, 4801 BC)
a = (14-month)/12 ! A will be 1 for January or February, and 0 for other months, with integer truncation
y = year + 4800 - a
m = month + 12*a - 3 ! M will be 0 for March and 11 for February
! All years in the BC era must be converted to astronomical years, so that 1BC is year 0, 2 BC is year "-1", etc.
! Convert to a negative number, then increment towards zero
! Staring from a Gregorian calendar date
jdn = day + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045 ! with integer truncation
! Finding the Julian Calendar date given the JDN (Julian day number) and time of day
julian = jdn + (hour-12)/24.0_realtime + (minute)/1440.0_realtime + second/86400.0_realtime
ierr=merge(-6,ierr, julian<0.0_realtime ) ! Julian Day must be non-negative
endif
end associate
end subroutine date_to_julian
subroutine say_hello()
use, intrinsic :: iso_fortran_env, only : compiler_version
use, intrinsic :: iso_fortran_env, only : compiler_options
character(len=*),parameter :: all='(*(g0,1x))'
character(len=*),parameter :: chs='(*(g0))'
character(len=2) :: ch, split
integer :: argument_length, istat, posix, dos, i
character(len=:),allocatable :: progname, options
call get_command_argument(number=0,length=argument_length)
if(allocated(progname))deallocate(progname)
allocate(character(len=argument_length) :: progname)
call get_command_argument (0, progname, status=istat)
print all, 'run date.....:',iso_8601()
if (istat == 0) then
print all, "program name.:" // trim (progname)
else
print all, "Could not get the program name " // trim (progname)
endif
print all, 'compiled by..:', compiler_version()
options=' '//compiler_options()
if(options /= '')then
print all, 'using options:'
! guess which one
posix=0
dos=0
do i=2,len(options)
ch=options(i-1:i)
select case(ch)
case(' -'); posix=posix+1
case(' /'); dos=dos+1
end select
enddo
split=merge(' -',' /',posix > 0)
do i=2,len(options)
ch=options(i-1:i)
if(ch == split)then
write(*,chs,advance='no')char(10),ch
else
write(*,chs,advance='no')ch(2:2)
endif
enddo
print all
endif
print all
end subroutine say_hello
function iso_8601()
! return date using ISO 8601 format at a resolution of seconds
character(len=8) :: dt
character(len=10) :: tm
character(len=5) :: zone
character(len=25) :: iso_8601
call date_and_time(dt, tm, zone)
ISO_8601 = dt(1:4)//'-'//dt(5:6)//'-'//dt(7:8) &
& //'T'// &
& tm(1:2)//':'//tm(3:4)//':'//tm(5:6) &
& //zone(1:3)//':'//zone(4:5)
end function iso_8601
impure elemental function irand(first,last)
!@(#) a random whole number from FIRST to LAST inclusive
use, intrinsic :: iso_fortran_env, only : dp=>real64
integer,intent(in) :: first,last
real(kind=dp) :: rand_val
integer :: irand
call random_number(rand_val)
irand=first+floor((last+1-first)*rand_val)
end function irand
subroutine catstat()
!@(#) on linux systems show process statistics. System-dependent
integer :: exitstat
integer :: cmdstat
character(len=255) :: cmdmsg
call execute_command_line("cat /proc/$PPID/status", exitstat=exitstat,cmdstat=cmdstat,cmdmsg=cmdmsg)
end subroutine catstat
end module M_tictoc
program doit
use,intrinsic :: iso_fortran_env, only : int8, int16, int32, int64
use M_tictoc, only : timer, say_hello, catstat
use M_case
character(len=*),parameter :: original = "abcdxyz ZXYDCBA _!@"
character(len=*),parameter :: originalu = "ABCDXYZ ZXYDCBA _!@"
character(len=*),parameter :: originall = "abcdxyz zxydcba _!@"
character(len=*),parameter :: gen='(*(g0,1x))'
character(len=:),allocatable :: s
type(timer) :: clock
namelist /vals/ clock
!
! To test variants of a procedure change HOW_MANY_TIMES
! and replace the NOSPACE* names with your procedure names and
! change the interface block to describe your procedure name
! the testprocedures module is where to put your variants
! or use some other module with the routines in it.
integer,parameter :: how_many_times=1000000
call say_hello()
! first a little confidence test
print *,"original: ",'[',original,']'
print *,"upper1: ",'[',upper1(original),']', upper1(original).eq.originalu
print *,"lower1: ",'[',lower1(original),']', lower1(original).eq.originall
print *,"upper2: ",'[',upper2(original),']', upper2(original).eq.originalu
print *,"lower2: ",'[',lower2(original),']', lower2(original).eq.originall
print *,"upper3: ",'[',upper3(original),']', upper3(original).eq.originalu
print *,"lower3: ",'[',lower3(original),']', lower3(original).eq.originall
print *,"upper4: ",'[',upper4(original),']', upper4(original).eq.originalu
print *,"lower4: ",'[',lower4(original),']', lower4(original).eq.originall
print *,"to_upper: ",'[',to_upper(original),']', to_upper(original).eq.originalu
print *,"to_lower: ",'[',to_lower(original),']', to_lower(original).eq.originall
! now some timing
clock=timer()
! PONDER: some would be faster the less changes there are
! PONDER: subroutines would potentially be faster than functions
call timeit('BASELINE',baseline)
call timeit('upper1',upper1)
call timeit('upper2',upper2)
call timeit('upper3',upper3)
call timeit('upper4',upper4)
call timeit('to_upper',to_upper)
call timeit('lower1',lower1)
call timeit('lower2',lower2)
call timeit('lower3',lower3)
call timeit('lower4',lower4)
call timeit('to_lower',to_lower)
block
use,intrinsic :: iso_fortran_env, only : int8,int16,int32,int64,real32,real64,real128
use,intrinsic :: iso_fortran_env, only : stderr=>ERROR_UNIT, stdin=>INPUT_UNIT, stdout=>OUTPUT_UNIT
character(len=*),parameter :: g='(*(g0,1x))'
integer(kind=int64) :: icount64, icount_rate64, icount_max64
real(kind=real64) :: frequency
print g,':SYSTEM_CLOCK PRECISION:'
call system_clock(icount64, icount_rate64, icount_max64)
print g,'COUNT_MAX(64bit)=' , icount_max64
print g,'COUNT_RATE(64bit)=' , icount_rate64
frequency=real(icount_max64,kind=int64)/real(icount_rate64,kind=real64)
print g,'FLIP EVERY N(secs)=' , frequency ,' (days)==>',frequency/86400_real64
print g,'CURRENT COUNT(64bit)=' , icount64
print g,'next flip(secs)=',dble(icount_max64-icount64)/icount_rate64,'(days)==>', dble(icount_max64-icount64)/icount_rate64/86400
endblock
call catstat()
contains
subroutine timeit(label,procedure)
! OPTIMIZING LOOP AWAY
! assuming the loop itself is not complex and possibly in need of unrolling and so on
! and that it is so simple the compiler is optimizing it away because nothing in the
! loop is used the loop may be identified as "dead code" and eliminated.
!
! + passing the procedure tends to prevent the loop being optimized away
! + using the volatile property also helps prevent loops from being optimized away
! + a write statement after the loop might also help, as well
! + finally, calling a function, particulary one writing values from a random pass helps
character(len=*),intent(in) :: label
character(len=:),volatile,allocatable :: t
interface
function procedure(string)
character(len=*),intent(in) :: string
character(len=len(string)) :: procedure
end function
end interface
integer :: i
print gen
call clock%tic()
do i=1,how_many_times
t=procedure('This is a TEST ')
enddo
call clock%toc()
call clock%print(label)
write(*,*)t
! write(*,*)clock
! write (*, nml=vals) ! using NAMELIST creates easy-to-postprocess logs
! print gen, label//':CPU TIME = ', clock%cputime()
end subroutine timeit
function baseline(line)
character(len=*),intent(in) :: line
character(len=len(line)) :: baseline
baseline=line
end function baseline
end program doit