Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Data type at runtime

jinliang_w_
Beginner
1,386 Views

Hi There,

I have a global allocatable vector, say n(:), defined in a module. It is big, in MB or even GB, with values either in the range -218~217, or -32768~32767, or -2147483648~ 2147483647, which can be represented by a 1-byte, 2-byte and 4 byte integer variable, respectively. The problem is, the value range can only be determined at runtime. I know I can define 3 vectors (say n1(:), n2(:), and n4(:)) and only allocate and use the right integer type of n vector at runtime. However, this makes the code quite cumbersome and also a bit slow (?), using a lot of if-else-then to determine which vector to allocate and use. In Fortran 2003, the polymorphic data type was introduced. However, it seems to be not applicable to my case, as the n(:) vector has to be repeatedly reset values, which are then used in the computation. 

Anyone can kindly help, please?

 

Thanks a lot!

Labels (1)
0 Kudos
6 Replies
FortranFan
Honored Contributor III
1,351 Views

@jinliang_w_ ,

If you can provide a specific description of what you seek and preferably with a code example (or two), some readers here may be able to help.

But otherwise, the responses are likely going to be the Fortran language does NOT include a convenient facility toward your interest.  I personally don't think polymorphism will help you either, at least based on your original post.

But a workaround for you may be to think along the lines of object-oriented (OO) code design and encapsulate the instructions of your data type in a MODULE (or even a derived type in a MODULE, perhaps even a parameterized derived type a la a 'class' per OO terminology).  And bring to bear the limited generic facilities in Fortran in this MODULE/'class' using the 'KIND' facility in Fortran.

Once you do this, then on the caller side you may be able to simplify matters to some extent by only requiring, say, a single IF..THEN..ELSE.. or better yet, a single SELECT CASE construct.

You may not find this to be "super great", but maybe you will see this as better than your other alternatives.

For an illustration using REAL(KIND) - as opposed to your situation which is with INTEGER kind - take a look at this link: https://community.intel.com/t5/Intel-Fortran-Compiler/REAL-KIND/m-p/1137892/highlight/true#M136254

You may get the idea(s) from this link.

0 Kudos
jimdempseyatthecove
Honored Contributor III
1,348 Views

The use of a polymorphic data type will not make the code any faster. While it will (may) hide the type, type selection dispatch, it will still be there.

Note, because of the convenience of having the runtime code make this type selection, the tendency is to then pass the polymorphic type throughout the application. IOW every step along the way will incorporate the dispatch (non-optimal coding).

The better route (from performance perspective) is to use three or four different sets of subroutines, each only differ in type, then select the type at the outer most call level. Elimination of redundant algorithms can be eliminated with use of INCLUDE.

Jim Dempsey

0 Kudos
jinliang_w_
Beginner
1,330 Views

Thank you both for the reply!

Maybe I am not quite clear what I want to do by NOT providing an example. Here it is

 

Module MyVar

  IMPLICIT NONE

  INTEGER, PARAMETER :: I4B = SELECTED_INT_KIND(9) 
  INTEGER(I4B), Allocatable :: N4(:)

END MODULE MyVar

 

Program Main

  USE MyVar

  INTEGER(I4B) :: NLen, NRange

  ! get NLen and NRange from an input file

  Allocate(N4(NLen))

  DO

       !Set values in N4(i), for i =1~NLen

       !Use N4 in computation

       !If the computational result satisfies certain conditions, exit DO,

       !    and the job is done

  END DO

END Program Main

 

The problem is that NLen (say in millions) can be large, resulting in a huge N4 vector with possibly in several GBs. On the other hand, the range of values of N4 might be -128~127, -32768~32767, or -2147483648~2147483647. If it is the first case, using a 1-byte integer vector would save 3/4 of RAM. So to save RAM, and possibly to make the program run faster, I could do the following.

 

Module MyVar

  IMPLICIT NONE

  INTEGER, PARAMETER :: I4B = SELECTED_INT_KIND(9) 

  INTEGER, PARAMETER :: I2B = SELECTED_INT_KIND(4)
  INTEGER, PARAMETER :: I1B = SELECTED_INT_KIND(2)

  INTEGER(I4B), Allocatable :: N4(:)

  INTEGER(I2B), Allocatable :: N2(:)

  INTEGER(I1B), Allocatable :: N1(:)

END MODULE MyVar

 

Program Main

  USE MyVar

  INTEGER(I4B) :: NLen, NRange

  ! get NLen and NRange from an input file

  IF(NRange  < 128) THEN

       Allocate(N1(NLen))

  ELSE IF(NRange  < 32768) THEN

       Allocate(N2(NLen))

  ELSE

       Allocate(N4(NLen))

  END IF

  DO

       !Set values in N4(i), N2(i) or N1(i) depending on NRange,  for i =1~NLen

       !Use N4(i), N2(i) or N1(i) depending on NRange in computation

       !If the computational result satisfies certain conditions, exit DO,

       !    and the job is done

  END DO

END Program Main

 

The first code is more elegant, but can waste 3/4 RAM and as a result can be slower. The 2ed uses the smallest possible RAM and as a result can run faster (less likely to miss cache?). However, the code becomes longer, especially considering that the N vector is used many times (places) within the do loop, and each time one has to use the if-else-then or select case to choose the right one.

 

Between short, concise code and long but efficient (in RAM and CPU use), I would prefer the latter. So between the above two options, I would like the 2nd one. However, I would like to check if there are better solutions in terms of both. I am not quite familiar with Fortran 2003 and later extensions.

 

 

 

0 Kudos
FortranFan
Honored Contributor III
1,311 Views

@jinliang_w_ ,

In case you are willing to consider current Fortran standard, you can review an example such as the following: this is a variant of what I show above but adapted somewhat to the example you listed:

module kinds_m
   use, intrinsic :: iso_fortran_env, only : I1 => int8, I2 => int16, &
                                             I4 => int32, I8 => int64
end module

module dat_m
   use kinds_m, only : I1, I2, I4, I8
   type :: dat_t(K, L)
      integer, kind :: K = I1
      integer, len :: L
      integer(K) :: n(L)
   contains
      procedure, pass(this) :: do_work_i1
      procedure, pass(this) :: do_work_i2
      procedure, pass(this) :: do_work_i4
      procedure, pass(this) :: do_work_i8
      generic :: do_work => do_work_i1, do_work_i2, do_work_i4, do_work_i8
   end type
contains
   subroutine do_work_i1( this ) !<-- add arguments as needed
      class(dat_t(K=I1,L=*)), intent(inout) :: this
      integer :: i
      do i = 1, this%l
         this%n(I) = I
      end do
      print *, "do_work_i1: this%n(1) = ", this%n(1)
   end subroutine
   subroutine do_work_i2( this ) !<-- add arguments as needed
      class(dat_t(K=I2,L=*)), intent(inout) :: this
      integer :: i
      do i = 1, this%l
         this%n(I) = I
      end do
      print *, "do_work_i2: this%n(1) = ", this%n(1)
   end subroutine
   subroutine do_work_i4( this ) !<-- add arguments as needed
      class(dat_t(K=I4,L=*)), intent(inout) :: this
      integer :: i
      do i = 1, this%l
         this%n(I) = I
      end do
      print *, "do_work_i4: this%n(1) = ", this%n(1)
   end subroutine
   subroutine do_work_i8( this ) !<-- add arguments as needed
      class(dat_t(K=I8,L=*)), intent(inout) :: this
      integer :: i
      do i = 1, this%l
         this%n(I) = I
      end do
      print *, "do_work_i8: this%n(1) = ", this%n(1)
   end subroutine
end module

program p
   use kinds_m, only : I1, I2, I4, I8
   use dat_m, only : dat_t
   integer :: nlen, nrange
   print *, "Enter nlen:"
   read *, nlen
   print "(*(g0,1x))", "Enter nrange: one of ", range(1_i1), range(1_i2), range(1_i4), range(1_i8)
   read *, nrange
   select case ( nrange )
      case ( range(1_i1) )
         blk_i1: block
            type(dat_t(K=I1,L=nlen)) :: dat
            call dat%do_work()
         end block blk_i1
      case ( range(1_i2) )
         blk_i2: block
            type(dat_t(K=I2,L=nlen)) :: dat
            call dat%do_work()
         end block blk_i2
      case ( range(1_i4) )
         blk_i4: block
            type(dat_t(K=I4,L=nlen)) :: dat
            call dat%do_work()
         end block blk_i4
      case ( range(1_i8) )
         blk_i8: block
            type(dat_t(K=I8,L=nlen)) :: dat
            call dat%do_work()
         end block blk_i8
      case default
         print *, "Invalid nrange"
   end select
end program

 

You can try executing the above code as follows:

C:\Temp>ifort /standard-semantics /warn:all /stand:f18 p.f90
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.3.311 Build 20201010_000000
Copyright (C) 1985-2020 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.26.28806.0
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:p.exe
-subsystem:console
p.obj

C:\Temp>p.exe
 Enter nlen:
3
Enter nrange: one of  2 4 9 18
2
 do_work_i1: this%n(1) =  1

C:\Temp>p.exe
 Enter nlen:
5
Enter nrange: one of  2 4 9 18
18
 do_work_i8: this%n(1) =  1

C:\Temp>

 

FortranFan
Honored Contributor III
1,309 Views

Note since your question is somewhat general in nature for Fortran, you can also consider inquiring at https://fortran-lang.discourse.group/ and comp.lang.fortran for broader feedback from other Fortran users.

0 Kudos
jinliang_w_
Beginner
1,296 Views

Thank you so much for the reply! 

0 Kudos
Reply