Definition of operator(+) with multiple addends in Fortran with derived type. An issue with allocatable array - oop

I am trying to define the (+) operator between Fortran derived types that describe matrices (linear operators).
My goal is to implicitly define a matrix M = M1 + M2 + M3 such that, given a vector x, Mx = M1x + M2x + M3x.
First, I defined an abstract type (abs_linop) with the abstract interface for a matrix vector multiplication (y = M *x).
Then, I built an derived type (add_linop), extending the abstract type (abs_linop).
The operator (+) is defined for the type (add_linop). I then create an example of concrete type (eye) extending the abstract type (abs_linop) that describes the identity matrix. This type is used in the main program. This is the source code
module LinearOperator
implicit none
private
public :: abs_linop,multiplication
type, abstract :: abs_linop
integer :: nrow=0
integer :: ncol=0
character(len=20) :: name='empty'
contains
!> Procedure for computation of (matrix) times (vector)
procedure(multiplication), deferred :: Mxv
end type abs_linop
abstract interface
!>-------------------------------------------------------------
!> Abstract procedure defining the interface for a general
!<-------------------------------------------------------------
subroutine multiplication(this,vec_in,vec_out,info,lun_err)
import abs_linop
implicit none
class(abs_linop), intent(inout) :: this
real(kind=8), intent(in ) :: vec_in(this%ncol)
real(kind=8), intent(inout) :: vec_out(this%nrow)
integer, optional, intent(inout) :: info
integer, optional, intent(in ) :: lun_err
end subroutine multiplication
end interface
!>---------------------------------------------------------
!> Structure variable for Identity matrix
!> (rectangular case included)
!>---------------------------------------------------------
type, extends(abs_linop), public :: eye
contains
!> Static constructor
procedure, public, pass :: init => init_eye
!> Compute matrix times vector operatoration
procedure, public, pass :: Mxv => apply_eye
end type eye
!>----------------------------------------------------------------
!> Structure variable to build implicit matrix defined
!> as composition and sum of linear operator
!>----------------------------------------------------------------
public :: add_linop, operator(+)
type, extends(abs_linop) :: add_linop
class(abs_linop) , pointer :: matrix_1
class(abs_linop) , pointer :: matrix_2
real(kind=8), allocatable :: scr(:)
contains
procedure, public , pass:: Mxv => add_Mxv
end type add_linop
INTERFACE OPERATOR (+)
module PROCEDURE mmsum
END INTERFACE OPERATOR (+)
contains
!>------------------------------------------------------
!> Function that give two linear operator A1 and A2
!> defines, implicitely, the linear operator
!> A=A1+A2
!> (public procedure for class add_linop)
!>
!> usage:
!> 'var' = A1 + A2
!<-------------------------------------------------------------
function mmsum(matrix_1,matrix_2) result(this)
implicit none
class(abs_linop), target, intent(in) :: matrix_1
class(abs_linop), target, intent(in) :: matrix_2
type(add_linop) :: this
! local
integer :: res
character(len=20) :: n1,n2
if (matrix_1%nrow .ne. matrix_2%nrow) &
write(*,*) 'Error mmproc dimension must agree '
if (matrix_1%ncol .ne. matrix_2%ncol) &
write(*,*) 'Error mmproc dimension must agree '
this%matrix_1 => matrix_1
this%matrix_2 => matrix_2
this%nrow = matrix_1%nrow
this%ncol = matrix_2%ncol
this%name=etb(matrix_1%name)//'+'//etb(matrix_2%name)
write(*,*) 'Sum Matrix initialization '
write(*,*) 'M1 : ',this%matrix_1%name
write(*,*) 'M2 : ',this%matrix_2%name
write(*,*) 'sum : ',this%name
allocate(this%scr(this%nrow),stat=res)
contains
function etb(strIn) result(strOut)
implicit none
! vars
character(len=*), intent(in) :: strIn
character(len=len_trim(adjustl(strIn))) :: strOut
strOut=trim(adjustl(strIn))
end function etb
end function mmsum
recursive subroutine add_Mxv(this,vec_in,vec_out,info,lun_err)
implicit none
class(add_linop), intent(inout) :: this
real(kind=8), intent(in ) :: vec_in(this%ncol)
real(kind=8), intent(inout) :: vec_out(this%nrow)
integer, optional, intent(inout) :: info
integer, optional, intent(in ) :: lun_err
write(*,*) 'Matrix vector multipliction',&
'matrix:',this%name,&
'M1: ',this%matrix_1%name,&
'M2: ',this%matrix_2%name
select type (mat=>this%matrix_1)
type is (add_linop)
write(*,*) 'is allocated(mat%scr) ?', allocated(mat%scr)
end select
call this%matrix_1%Mxv(vec_in,this%scr,info=info,lun_err=lun_err)
call this%matrix_2%Mxv(vec_in,vec_out,info=info,lun_err=lun_err)
vec_out = this%scr + vec_out
end subroutine add_Mxv
subroutine init_eye(this,nrow)
implicit none
class(eye), intent(inout) :: this
integer, intent(in ) :: nrow
this%nrow = nrow
this%ncol = nrow
end subroutine init_eye
subroutine apply_eye(this,vec_in,vec_out,info,lun_err)
class(eye), intent(inout) :: this
real(kind=8), intent(in ) :: vec_in(this%ncol)
real(kind=8), intent(inout) :: vec_out(this%nrow)
integer, optional, intent(inout) :: info
integer, optional, intent(in ) :: lun_err
! local
integer :: mindim
vec_out = vec_in
if (present(info)) info=0
end subroutine apply_eye
end module LinearOperator
program main
use LinearOperator
implicit none
real(kind=8) :: x(2),y(2),z(2),t(2)
type(eye) :: id1,id2,id3
type(add_linop) :: sum12,sum23,sum123_ok,sum123_ko
integer :: i
call id1%init(2)
id1%name='I1'
call id2%init(2)
id2%name='I2'
call id3%init(2)
id3%name='I3'
x=1.0d0
y=1.0d0
z=1.0d0
write(*,*) ' Vector x =', x
call id1%Mxv(x,t)
write(*,*) ' Vector t = I1 *x', t
write(*,*) ' '
sum12 = id1 + id2
call sum12%Mxv(x,t)
write(*,*) ' Vector t = (I1 +I2) *x', t
write(*,*) ' '
sum23 = id2 + id3
sum123_ok = id1 + sum23
call sum123_ok%Mxv(x,t)
write(*,*) ' Vector t = ( I1 + (I2 + I3) )*x', t
write(*,*) ' '
sum123_ko = id1 + id2 + id3
call sum123_ko%Mxv(x,t)
write(*,*) ' Vector t = ( I1 +I2 + I3) *x', t
end program main
I compile this code with gfortran version 7.5.0 and flags
"-g -C -Wall -fcheck=all -O -ffree-line-length-none -mcmodel=medium "
and this is what I get
Vector x = 1.0000000000000000 1.0000000000000000
Vector t = I1 *x 1.0000000000000000 1.0000000000000000
Sum Matrix initialization
M1 : I1
M2 : I2
sum : I1+I2
Matrix vector multiplictionmatrix:I1+I2 M1: I1 M2: I2
Vector t = (I1 +I2) *x 2.0000000000000000 2.0000000000000000
Sum Matrix initialization
M1 : I2
M2 : I3
sum : I2+I3
Sum Matrix initialization
M1 : I1
M2 : I2+I3
sum : I1+I2+I3
Matrix vector multiplictionmatrix:I1+I2+I3 M1: I1 M2: I2+I3
Matrix vector multiplictionmatrix:I2+I3 M1: I2 M2: I3
Vector t = ( I1 + (I2 + I3) )*x 3.0000000000000000 3.0000000000000000
Sum Matrix initialization
M1 : I1
M2 : I2
sum : I1+I2
Sum Matrix initialization
M1 : I1+I2
M2 : I3
sum : I1+I2+I3
Matrix vector multiplictionmatrix:I1+I2+I3 M1: I1+I2 M2: I3
is allocated(mat%scr) ? F
Matrix vector multiplictionmatrix:I1+I2 M1: I1 M2: I2
At line 126 of file LinearOperator.f90
Fortran runtime error: Allocatable actual argument &apos;this&apos; is not allocated
Everthing works fine when I use the (+) operator with 2 terms. But when 3 terms are used there is an issue with the allocatable array scr, member of type (add_linop), that is not allocated.
Does anybody knows the reason of this issue and how to solve it?
I include the Makefile used for compiling the code.
#Gfortran compiler
FC = gfortran
OPENMP = -fopenmp
MODEL = -mcmodel=medium
OFLAGS = -O5 -ffree-line-length-none
DFLAGS = -g -C -Wall -fcheck=all -O -ffree-line-length-none
#DFLAGS = -g -C -Wall -ffree-line-length-none -fcheck=all
PFLAGS = -pg
CPPFLAGS = -D_GFORTRAN_COMP
ARFLAGS =
ODIR = objs
MDIR = mods
LDIR = libs
INCLUDE = -J$(MODDIR)
OBJDIR = $(CURDIR)/$(ODIR)
MODDIR = $(CURDIR)/$(MDIR)
LIBDIR = $(CURDIR)/$(LDIR)
INCLUDE += -I$(MODDIR)
FFLAGS = $(OFLAGS) $(MODEL) $(INCLUDE)
LIBSRCS =
DEST = .
EXTHDRS =
HDRS =
LIBS = -llapack -lblas
LIBMODS =
LDFLAGS = $(MODEL) $(INCLUDE) -L. -L/usr/lib -L/usr/local/lib -L$(LIBDIR)
LINKER = $(FC)
MAKEFILE = Makefile
PRINT = pr
CAT = cat
PROGRAM = main.out
SRCS = LinearOperator.f90
OBJS = LinearOperator.f90
PRJS= $(SRCS:jo=.prj)
OBJECTS = $(SRCS:%.f90=$(OBJDIR)/%.o)
MODULES = $(addprefix $(MODDIR)/,$(MODS))
.SUFFIXES: .prj .f90
print-% :
#echo $* = $($*)
.f.prj:
ftnchek -project -declare -noverbose $<
.f90.o:
$(FC) $(FFLAGS) $(INCLUDE) -c $<
all::
#make dirs
#make $(PROGRAM)
$(PROGRAM): $(LIBS) $(MODULES) $(OBJECTS)
$(LINKER) -o $(PROGRAM) $(LDFLAGS) $(OBJECTS) $(LIBS)
$(LIBS):
#set -e; for i in $(LIBSRCS); do cd $$i; $(MAKE) --no-print-directory -e CURDIR=$(CURDIR); cd $(CURDIR); done
$(OBJECTS): $(OBJDIR)/%.o: %.f90
$(FC) $(CPPFLAGS) $(FFLAGS) -o $# -c $<
dirs:
#-mkdir -p $(OBJDIR) $(MODDIR) $(LIBDIR)
clean-emacs:
#-rm -f $(CURDIR)/*.*~
#-rm -f $(CURDIR)/*\#*
check: $(PRJS)
ftnchek -noverbose -declare $(PRJS) -project -noextern -library > $(PROGRAM).ftn
profile:; #make "FFLAGS=$(PFLAGS) $(MODEL) " "CFLAGS=$(PFLAGS) $(MODEL)" "LDFLAGS=$(PFLAGS) $(LDFLAGS)" $(PROGRAM)
debug:; #make "FFLAGS=$(DFLAGS) $(MODEL) $(INCLUDE)" "LDFLAGS=$(DFLAGS) $(LDFLAGS)" $(PROGRAM)
openmp:; #make "FFLAGS=$(OFLAGS) $(OPENMP) $(MODEL) $(INCLUDE)" "LDFLAGS=$(LDFLAGS) $(OPENMP)" $(PROGRAM)
clean:; #rm -f $(OBJECTS) $(MODULES) $(PROGRAM).cat $(PROGRAM).ftn
#set -e; for i in $(LIBSRCS); do cd $$i; $(MAKE) --no-print-directory clean; cd $(CURDIR); done
clobber:; #rm -f $(OBJECTS) $(MODULES) $(PROGRAM).cat $(PROGRAM).ftn $(PROGRAM)
#-rm -rf $(OBJDIR) $(MODDIR) $(LIBDIR)
#-rm -f $(CURDIR)/*.*~
#-rm -f $(CURDIR)/*\#*
.PHONY: mods
index:; ctags -wx $(HDRS) $(SRCS)
install: $(PROGRAM)
install -s $(PROGRAM) $(DEST)
print:; $(PRINT) $(HDRS) $(SRCS)
cat:; $(CAT) $(HDRS) $(SRCS) > $(PROGRAM).cat
program: $(PROGRAM)
profile: $(PROFILE)
tags: $(HDRS) $(SRCS); ctags $(HDRS) $(SRCS)
update: $(DEST)/$(PROGRAM)
main.o: linearoperator.mod
# DO NOT EDIT --- auto-generated file
linearoperator.mod : LinearOperator.f90
$(FC) $(FCFLAGS) -c $<

Your program is not valid Fortran.
The function result of mmsum has a pointer component which, during the execution of the function, is pointer associated with a dummy argument. This dummy argument (correctly for this use) has the target attribute. However, the actual argument does not have the target attribute: when the function execution completes the pointer component becomes of undefined pointer association status.
In the subroutine add_Mxv there is an attempt to dereference this pointer. This is not allowed.
It will be necessary to revisit how the operands are handled in your data type. Note in particular that an expression cannot have the target attribute: in the case of id1+id2+id3 the id1+id2 expression won't usefully remain as something to reference later on.

Related

Fortran "undefined reference to [function]" error that goes away by compiling the .f90 files directly

Before someone closes the question, yes, there are many questions that seem similar, but so far I haven't found one with this exact weird problem that seems to go away only sometimes.
I had an odd Fortran error while trying to make a module for linear regression.
The module is named "LSQregression" and the main program "LSQAdvertising". Compiling it using the following gfortran command works:
gfortran ../LSQregression.f90 LinearAdvertising.f90 -llapack -o LinAd
However, I'd like to be able to turn my module into a .o file and link it with whatever program I may need instead of compiling again every time. So I tried to do that:
gfortran -c ../LSQregression.f90
And then link it with my program file like that:
gfortran ../LSQregression.o LinearAdvertising.f90 -llapack -o LinAd
I also tried also turning the program into a .o file. Neither works, they both return the following error:
/usr/bin/ld: /tmp/ccf7T1aj.o: in function `MAIN__':
LinearAdvertising.f90:(.text+0x1f8b): undefined reference to `__lsqregression_MOD_lsqestimate_simple'
collect2: error: ld returned 1 exit status
It refers to the following function in the module that is being called here inside the program:
print *, LSQestimate(test,LSQbeta(inputs,sales,(/1,2/)) )
The function itself is defined as part of an interface:
interface LSQestimate
procedure LSQestimate_simple, LSQestimate_using
end interface LSQestimate
real(8) function LSQestimate_simple(X, beta)
implicit none
real(8), dimension(:,:) :: X, beta
real(8) :: boundary, Y
integer :: i, Xlen, betalen
Xlen = size(X, 1)
betalen = size(beta, 1)
if (Xlen + 1 .ne. betalen) stop "incompatible beta and X"
Y = beta(1,1)
do i = 1, Xlen
Y = Y + beta(i+1, 1)*X(i,1)
end do
LSQestimate_simple = Y
end function LSQestimate_simple
It's very odd because I've done the same thing with another function and it seems to work fine, and the problem only happens when I turn the module into a .o file first, and goes away if I try to directly compile the .f90 file... I can't figure out why one works and the other doesn't.
EDIT: Someone told me to do nm ../LSQregression.o | grep -i regression . I have no idea what that does but I did it so here are the results if it helps at all:
0000000000002768 T __lsqregression_MOD_inv
0000000000000b85 T __lsqregression_MOD_lsqbeta_simple
000000000000035c T __lsqregression_MOD_lsqbeta_using
0000000000000000 T __lsqregression_MOD_lsqdecision
Oddly enough the name of the function in question doesn't even appear here.
EDIT 2: Decided to post the entire code of the module:
module LSQregression
implicit none
public :: LSQbeta, LSQestimate
interface LSQbeta
procedure LSQbeta_simple, LSQbeta_using
end interface LSQbeta
interface LSQestimate
procedure LSQestimate_simple, LSQestimate_using
end interface LSQestimate
contains
function inv(A) result(Ainv)
implicit none
real(8), dimension(:,:), intent(in) :: A
real(8), dimension(size(A,1),size(A,2)) :: Ainv
real(8), dimension(size(A,1)) :: work ! work array for LAPACK
integer, dimension(size(A,1)) :: ipiv ! pivot indices
integer :: n, info
! External procedures defined in LAPACK
external DGETRF
external DGETRI
! Store A in Ainv to prevent it from being overwritten by LAPACK
Ainv = A
n = size(A,1)
! DGETRF computes an LU factorization of a general M-by-N matrix A
! using partial pivoting with row interchanges.
call DGETRF(n, n, Ainv, n, ipiv, info)
if (info /= 0) then
stop 'Matrix is numerically singular!'
end if
! DGETRI computes the inverse of a matrix using the LU factorization
! computed by DGETRF.
call DGETRI(n, Ainv, n, ipiv, work, n, info)
if (info /= 0) then
stop 'Matrix inversion failed!'
end if
end function inv
!----------------------------------------------------------------------
function LSQbeta_simple(Xold, Y) result(beta)
real(8), dimension(:,:) :: Xold, Y
real(8), dimension(size(Xold,1), size(Xold,2)+1) :: X
real(8), dimension(:,:), allocatable :: beta, XTX, IXTX, pinv
integer :: rowsX, colsX, rowsY, colsY,i
rowsX = size(X(:,1))
colsX = size(X(1,:))
rowsY = size(Y(:,1))
colsY = size(Y(1,:))
if (colsY .ne. 1) stop 'Inappropriate y'
if (rowsY .ne. rowsX) stop "Y and X rows don't match"
!---X-ify-----------
X(:,1) = 1.0 !includes 1 in the first column
do i = 2, colsX
X(:,i) = Xold(:,i-1)
end do
!--------------------
allocate(XTX(colsX,colsX))
allocate(IXTX(colsX,colsX))
allocate(pinv(colsX,rowsY))
allocate(beta(colsX,1))
XTX = matmul(transpose(X),X)
IXTX = inv(XTX)
pinv = matmul(IXTX, transpose(X))
beta = matmul(pinv, Y)
deallocate(pinv)
deallocate(XTX)
deallocate(IXTX)
return
deallocate(beta)
end function LSQbeta_simple
!--------------------------------------------
function LSQbeta_using(Xold, Y, indexes) result(beta)
implicit none
real(8), dimension(:,:) :: Xold, Y
integer, dimension(:) :: indexes
real(8), dimension(size(indexes)+1,1) :: beta
real(8), dimension(size(Xold,1),size(indexes)) :: X
integer :: indLen,i
indLen = size(indexes)
do i = 1, indLen
X(:,i) = Xold(:,indexes(i))
end do
beta = LSQbeta_simple(X,Y)
end function LSQbeta_using
real(8) function LSQestimate_simple(X, beta)
implicit none
real(8), dimension(:,:) :: X, beta
real(8) :: boundary, Y
integer :: i, Xlen, betalen
Xlen = size(X, 1)
betalen = size(beta, 1)
if (Xlen + 1 .ne. betalen) stop "incompatible beta and X"
Y = beta(1,1)
do i = 1, Xlen
Y = Y + beta(i+1, 1)*X(i,1)
end do
LSQestimate_simple = Y
end function LSQestimate_simple
real(8) function LSQestimate_using()
LSQestimate_using = 1.0D0
end function LSQestimate_using
logical function LSQdecision(X, beta, boundary)
implicit none
real(8), dimension(:,:) :: X, beta
real(8) :: boundary
LSQdecision = LSQestimate(X, beta) > boundary
end function LSQdecision
end module LSQregression

Performance of Fortran versus MPI files writing/reading

I am confused about the performance of Fortran writing and reading performance (speed) versus MPI one for small and big files.
I wrote the following simple dummy program to test this (just writing dummy values to files):
PROGRAM test
!
IMPLICIT NONE
!
#if defined (__MPI)
!
! Include file for MPI
!
#if defined (__MPI_MODULE)
USE mpi
#else
INCLUDE 'mpif.h'
#endif
#else
! dummy world and null communicator
INTEGER, PARAMETER :: MPI_COMM_WORLD = 0
INTEGER, PARAMETER :: MPI_COMM_NULL = -1
INTEGER, PARAMETER :: MPI_COMM_SELF = -2
#endif
INTEGER (kind=MPI_OFFSET_KIND) :: lsize, pos, pos2
INTEGER, PARAMETER :: DP = 8
REAL(kind=DP), ALLOCATABLE, DIMENSION(:) :: trans_prob, array_cpu
INTEGER :: ierr, i, error, my_pool_id, world_comm
INTEGER (kind=DP) :: fil
REAL :: start, finish
INTEGER :: iunepmat, npool, arr_size, loop, pos3, j
real(dp):: dummy
integer*8 :: unf_recl
integer :: ios, direct_io_factor, recl
iunepmat = 10000
arr_size = 102400
loop = 500
! Initialize MPI
CALL MPI_INIT(ierr)
call MPI_COMM_DUP(MPI_COMM_WORLD, world_comm, ierr)
call MPI_COMM_RANK(world_comm,my_pool_id,error)
ALLOCATE(trans_prob(arr_size))
trans_prob(:) = 1.5d0
!Write using Fortran
CALL MPI_BARRIER(world_comm,error)
!
CALL cpu_time(start)
!
DO i=1, loop
! This writes also info on the record length using a real with 4 bytes.
OPEN(unit=10+my_pool_id, form='unformatted', position='append', action='write')
WRITE(10+my_pool_id ) trans_prob(:)
CLOSE(unit=10+my_pool_id)
ENDDO
CALL MPI_COMM_SIZE(world_comm, npool, error)
! Master collect and write
IF (my_pool_id==0) THEN
INQUIRE (IOLENGTH=direct_io_factor) dummy
unf_recl = direct_io_factor * int(arr_size * loop, kind=kind(unf_recl))
ALLOCATE (array_cpu( arr_size * loop ))
array_cpu(:) = 0.0d0
OPEN(unit=100,file='merged.dat',form='unformatted', status='new', position='append', action='write')
DO i=0, npool - 1
OPEN(unit=10+i,form='unformatted', status ='old', access='direct', recl = unf_recl )
READ(unit=10+i, rec=1) array_cpu(:)
CLOSE(unit=10+i)
WRITE(unit=100) array_cpu(:)
ENDDO
CLOSE(unit=100)
DEALLOCATE (array_cpu)
ENDIF
call cpu_time(finish)
!Print time
CALL MPI_BARRIER(world_comm,error)
IF (my_pool_id==0) print*, ' Fortran time', finish-start
!Write using MPI
CALL MPI_BARRIER(world_comm,error)
!
CALL cpu_time(start)
!
lsize = INT( arr_size , kind = MPI_OFFSET_KIND)
pos = 0
pos2 = 0
CALL MPI_FILE_OPEN(world_comm, 'MPI.dat',MPI_MODE_WRONLY + MPI_MODE_CREATE,MPI_INFO_NULL,iunepmat,ierr)
DO i=1, loop
pos = pos2 + INT( arr_size * (my_pool_id), kind = MPI_OFFSET_KIND ) * 8_MPI_OFFSET_KIND
CALL MPI_FILE_SEEK(iunepmat, pos, MPI_SEEK_SET, ierr)
CALL MPI_FILE_WRITE(iunepmat, trans_prob, lsize, MPI_DOUBLE_PRECISION,MPI_STATUS_IGNORE,ierr)
pos2 = pos2 + INT( arr_size * (npool -1), kind = MPI_OFFSET_KIND ) * 8_MPI_OFFSET_KIND
ENDDO
!
CALL MPI_FILE_CLOSE(iunepmat,ierr)
CALL cpu_time(finish)
CALL MPI_BARRIER(world_comm,error)
IF (my_pool_id==0) print*, ' MPI time', finish-start
DEALLOCATE (trans_prob)
END PROGRAM
The compilation is made with:
mpif90 -O3 -x f95-cpp-input -D__FFTW -D__MPI -D__SCALAPACK test_mpi2.f90 -o a.x
and then run in parallel with 4 cores:
mpirun -np 4 ./a.x
I get the following results:
Loop size 1
array size 10,240,000
File size: 313 Mb
Fortran time 0.237030014 sec
MPI time 0.164155006 sec
Loop size 10
array size 1,024,000
File size: 313 Mb
Fortran time 0.242821991 sec
MPI time 0.172048002 sec
Loop size 100
array size 102,400
File size: 313 Mb
Fortran time 0.235879987 sec
MPI time 9.78289992E-02 sec
Loop size 50
array size 1,024,000
File size: 1.6G
Fortran time 1.60272002 sec
MPI time 3.40623116 sec
Loop size 500
array size 102,400
File size: 1.6G
Fortran time 1.44547606 sec
MPI time 3.38340592 sec
As you can see the performances of MPI degrade significantly for larger files. Is it possible to improve MPI performance for large files ?
Is this behavior expected?

How to make a matrix where its elements are functions, operate with them and the result still be a function?

I'm using Fortran I'm trying to create matrices where their elements are functions. Also I'd like to operate with them and the result still be a function. So here is what I try
module Greeninverse
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
real(dp), public, parameter :: wl = 1d0
real(dp), public, parameter :: wr = 1d0
integer, public, parameter :: matrix_size = 5
type ptr_wrapper
procedure(f), nopass, pointer :: func
end type ptr_wrapper
abstract interface
function f(x1,x2)
import
real(dp), intent(in) :: x1
real(dp), intent(in) :: x2
complex (dp), dimension(matrix_size,matrix_size):: f
end function f
end interface
contains
function Sigma(x1) result(S)
real(dp),intent(in) :: x1
complex(dp), dimension(matrix_size,matrix_size) :: S
real(dp):: aux_wr1,aux_wl1
complex(dp) :: S11, Snn
integer :: i,j
aux_wr1 = 1-x1**2/(2d0*wr)
aux_wl1 = 1-x1**2/(2d0*wl)
S11 = dcmplx(.5*(x1**2-2d0*wl), 2.0*wL*dsqrt(1-aux_wL1**2))
Snn = dcmplx(.5*(x1**2-2d0*wr), 2.0*wr*dsqrt(1-aux_wr1**2))
do i = 1, matrix_size
do j=i,matrix_size
S(i,j) = 0d0
S(j,i) = 0d0
end do
end do
S(1,1) = S11
S(matrix_size,matrix_size) = Snn
end function Sigma
function Omega(x1) result(Om)
real(dp),intent(in) :: x1
real(dp),dimension(matrix_size, matrix_size) :: Om
integer :: i,j
do i=1,matrix_size
do j= i, matrix_size
Om(i,j) = 0d0
Om(j,i) = 0d0
end do
end do
do i = 1,matrix_size
Om(i,i) = x1**2
end do
end function Omega
! Now I'd like to add them and take the inverse of the sum and still be a function
function Inversa(x1,x2) result (G0inv)
real(dp), intent(in) :: x1
real(dp), intent(in) :: x2
complex(dp), dimension(matrix_size,matrix_size) :: G0inv
complex(dp),dimension(matrix_size,matrix_size) :: Gaux
! Down here all these variables are needed by ZGETRF and ZGETRI
DOUBLE PRECISION, ALLOCATABLE, DIMENSION(:) :: WORK
Integer:: LWORK = matrix_size*matrix_size
Integer, Allocatable, dimension(:) :: IPIV
Integer :: INFO, LDA = matrix_size, M = matrix_size, N = matrix_size
Integer DeAllocateStatus
external liblapack
allocate(work(Lwork))
allocate(IPIV(N))
Gaux = Omega(x1)+Sigma(x2)
CALL ZGETRF (M, N, Gaux, LDA, IPIV, INFO)
! This calculates LU descomposition of a matrix and overwrites it
CALL ZGETRI(N, Gaux, N, IPIV, WORK, LWORK, INFO)
! This calculates the inverse of a matrix knowing its LU descomposition and overwrites it
G0inv = Gaux
end function Inversa
! Now I'd like to derive it
function Derivate(x1,x2,G) result(d)
! This function is supposed to derivate a matrix which its elements are functions but of two variables; x1 and x2. And it only derives respect the first variable
implicit none
real(dp), intent(in) :: x1
real(dp), intent(in) :: x2
procedure(f),pointer:: G
complex(dp),dimension(matrix_size,matrix_size) :: d
real(dp) :: h = 1.0E-6
d = (1.0*G(x1-2*h,x2) - 8.0*G(x1-h,x2) + 8.0*G(x1+h,x2) - 1.0*G(x1+2*h,x2))/(12.0*h)
end function Derivate
end module Greeninverse
program Greentest3
use, intrinsic :: iso_fortran_env, only: dp => real64
use Greeninverse
implicit none
real(dp) :: W(matrix_size,matrix_size)
complex(dp) :: S(matrix_size,matrix_size)
complex(dp) :: G(matrix_size,matrix_size)
complex(dp) :: DD(matrix_size,matrix_size)
W(:,:) = Omega(1d0)
S(:,:) = Sigma(2d0)
G(:,:) = Inversa(1d0,2d0)
DD(:,:) = Derivate(1d0,2d0,Inversa)
print*, W
print*, S
print*, G
print*, DD
end program Greentest3
The problem is in the function Derivate that I don't know how to say that the argument G is a matrix function and because of that I get an error message
DD(:,:) = Derivate(1d0,2d0,Inversa)
1
Error: Expected a procedure pointer for argument ‘g’ at (1)
That's why I use the abstract interface that it's supposed to say that is a function but it doesn't work as I expected
I tried also to make a pointer in the module section, that is
type(ptr_wrapper) :: DD(matrix_size,matrix_size)
but I get an error message
Error: Unexpected data declaration statement in CONTAINS section at (1)
I'd like to make all the matrices in the module section and in the program just evaluate them in the values of interest.
What am I doing wrong?
Looking at the function Derivate the dummy argument G is declared like
procedure(f), pointer:: G
This is a procedure pointer. The error message confirms that.
The actual argument to be passed to Derivate is, in this case, expected also to be a procedure pointer. Let's look at what the argument is:
DD(:,:) = Derivate(...,Inversa)
Inversa is a procedure (function), defined in the module. It, crucially, isn't a procedure pointer. So, indeed, the compiler complains.
Well, how do we go about fixing this? There are three obvious approaches:
have the actual argument a procedure pointer;
have the dummy argument a procedure (non-pointer);
allow argument association between a pointer and non-pointer.
For the first, the main program could have
procedure(f), pointer :: Inversa_ptr ! We've a procedure pointer...
Inversa_ptr => Inversa ! ... which we point at our procedure...
DD(:,:) = Derivate(...,Inversa_ptr) ! ... and is then the argument
For the Derivate as it is implemented, it doesn't use the pointer nature of the argument G: just the target is referenced. This means that the other two options become available.
We can make the dummy argument not a pointer, having
function Derivate(...,G)
procedure(f) :: G
end function
used like
DD(:,:) = Derivate(...,Inversa)
The third of our choices comes from defining the dummy argument as
function Derivate(...,G)
procedure(f), pointer, intent(in) :: G
end function
where, again, the reference is as in the second case.
When the dummy argument procedure pointer has the intent(in) attribute, it is allowed to be associated with a non-pointer procedure which is a valid target in pointer assignment. In this case G becomes pointer associated with that actual argument procedure (and because of the intent, that status can't be changed in the function).

Segmentation fault with deferred functions and non_overridable keyword

I am developing an object-oriented Fortran code for numerical optimization with polymorphism supported by abstract types. Because it is a good TDD practice, I'm trying to write all optimization tests in the abstract type class(generic_optimizer), which then should be run by each instantiated class, e.g., by type(newton_raphson).
All the optimization tests feature a call to call my_problem%solve(...), which is defined as deferred in the abstract type and of course features a different implementation in each derived type.
The issue is: if in each non-abstract class I define the deferred function as non_overridable, I get segmentation fault such as:
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) where
#0 0x0000000000000000 in ?? ()
#1 0x0000000000913efe in __newton_raphson_MOD_nr_solve ()
#2 0x00000000008cfafa in MAIN__ ()
#3 0x00000000008cfb2b in main ()
#4 0x0000003a3c81ed5d in __libc_start_main () from /lib64/libc.so.6
#5 0x00000000004048f9 in _start ()
After some trial-and-error, I've noticed that I can avoid the error if I remove the non_overridable declaration. In this case it is not an issue, but I wanted to enforce that since two levels of polymorphism are unlikely for this code. Was I violating any requirements from the standard, instead?
Here is a sample code that reproduces the error. I've been testing it with gfortran 5.3.0 and 6.1.0.
module generic_type_module
implicit none
private
type, abstract, public :: generic_type
real(8) :: some_data
contains
procedure (sqrt_interface), deferred :: square_root
procedure, non_overridable :: sqrt_test
end type generic_type
abstract interface
real(8) function sqrt_interface(this,x) result(sqrtx)
import generic_type
class(generic_type), intent(in) :: this
real(8), intent(in) :: x
end function sqrt_interface
end interface
contains
subroutine sqrt_test(this,x)
class(generic_type), intent(in) :: this
real(8), intent(in) :: x
print *, 'sqrt(',x,') = ',this%square_root(x)
end subroutine sqrt_test
end module generic_type_module
module actual_types_module
use generic_type_module
implicit none
private
type, public, extends(generic_type) :: crashing
real(8) :: other_data
contains
procedure, non_overridable :: square_root => crashing_square_root
end type crashing
type, public, extends(generic_type) :: working
real(8) :: other_data
contains
procedure :: square_root => working_square_root
end type working
contains
real(8) function crashing_square_root(this,x) result(sqrtx)
class(crashing), intent(in) :: this
real(8), intent(in) :: x
sqrtx = sqrt(x)
end function crashing_square_root
real(8) function working_square_root(this,x) result(sqrtx)
class(working), intent(in) :: this
real(8), intent(in) :: x
sqrtx = sqrt(x)
end function working_square_root
end module actual_types_module
program deferred_test
use actual_types_module
implicit none
type(crashing) :: crashes
type(working) :: works
call works%sqrt_test(2.0_8)
call crashes%sqrt_test(2.0_8)
end program
To narrow down the problem, I removed the abstract attribute and data members from the OP's code such that
module types
implicit none
type :: Type1
contains
procedure :: test
procedure :: square => Type1_square
endtype
type, extends(Type1) :: Type2
contains
procedure, non_overridable :: square => Type2_square
endtype
contains
subroutine test( this, x )
class(Type1) :: this
real :: x
print *, "square(", x, ") = ",this % square( x )
end subroutine
function Type1_square( this, x ) result( y )
class(Type1) :: this
real :: x, y
y = -100 ! dummy
end function
function Type2_square( this, x ) result( y )
class(Type2) :: this
real :: x, y
y = x**2
end function
end module
program main
use types
implicit none
type(Type1) :: t1
type(Type2) :: t2
call t1 % test( 2.0 )
call t2 % test( 2.0 )
end program
With this code, gfortran-6 gives
square( 2.00000000 ) = -100.000000
square( 2.00000000 ) = -100.000000
while ifort-{14,16} and Oracle fortran 12.5 give
square( 2.000000 ) = -100.0000
square( 2.000000 ) = 4.000000
I also tried replacing the functions with subroutines (to print which routines are actually called):
subroutine test( this, x )
class(Type1) :: this
real :: x, y
call this % square( x, y )
print *, "square(", x, ") = ", y
end subroutine
subroutine Type1_square( this, x, y )
class(Type1) :: this
real :: x, y
print *, "Type1_square:"
y = -100 ! dummy
end subroutine
subroutine Type2_square( this, x, y )
class(Type2) :: this
real :: x, y
print *, "Type2_square:"
y = x**2
end subroutine
with all the other parts kept the same. Then, gfortran-6 gives
Type1_square:
square( 2.00000000 ) = -100.000000
Type1_square:
square( 2.00000000 ) = -100.000000
while ifort-{14,16} and Oracle fortran 12.5 give
Type1_square:
square( 2.000000 ) = -100.0000
Type2_square:
square( 2.000000 ) = 4.000000
If I remove non_overridable from the above codes, gfortran gives the same result as the other compilers. So, this may be a specific issue to gfortran + non_overridable (if the above code is standard-conforming)...
(The reason why OP got segmentation fault may be that gfortran accessed the deferred procedure in the parent type (generic_type) having null pointer; if this is the case, the story becomes consistent.)
Edit
The same exceptional behavior of gfortran occurs also when we declare Type1 as abstract. Specifically, if we change the definition of Type1 as
type, abstract :: Type1 ! now an abstract type (cannot be instantiated)
contains
procedure :: test
procedure :: square => Type1_square
endtype
and the main program as
program main
use types
implicit none
type(Type2) :: t2
call t2 % test( 2.0 )
end program
we get
ifort-16 : square( 2.000000 ) = 4.000000
oracle-12.5 : square( 2.0 ) = 4.0
gfortran-6 : square( 2.00000000 ) = -100.000000
If we further make square() in Type1 to be deferred (i.e., no implementation given) and so make the code almost equivalent to the OP's case,
type, abstract :: Type1 ! now an abstract type (cannot be instantiated)
contains
procedure :: test
procedure(Type1_square), deferred :: square ! has no implementation yet
endtype
abstract interface
function Type1_square( this, x ) result( y )
import
class(Type1) :: this
real :: x, y
end function
end interface
then ifort-16 and Oracle-12.5 gives 4.0 with call t2 % test( 2.0 ), while gfortran-6 results in segmentation fault. Indeed, if we compile as
$ gfortran -fsanitize=address test.f90 # on Linux x86_64
we get
ASAN:SIGSEGV (<-- or "ASAN:DEADLYSIGNAL" on OSX 10.9)
=================================================================
==22045==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
(pc 0x000000000000 bp 0x7fff1d23ecd0 sp 0x7fff1d23eac8 T0)
==22045==Hint: pc points to the zero page.
So overall, it seems as if the binding name square() in Type1 (which has no implementation) is called erroneously by gfortran (possibly with null pointer). And more importantly, if we drop non_overridable from the definition of Type2, gfortran also gives 4.0 (with no segmentation fault).

Make/makefile progress indication!

Look at this makefile, it has some sort of primitive progress indication (could have been a progress bar).
Please give me suggestions/comments on it!
# BUILD is initially undefined
ifndef BUILD
# max equals 256 x's
sixteen := x x x x x x x x x x x x x x x x
MAX := $(foreach x,$(sixteen),$(sixteen))
# T estimates how many targets we are building by replacing BUILD with a special string
T := $(shell $(MAKE) -nrRf $(firstword $(MAKEFILE_LIST)) $(MAKECMDGOALS) \
BUILD="COUNTTHIS" | grep -c "COUNTTHIS")
# N is the number of pending targets in base 1, well in fact, base x :-)
N := $(wordlist 1,$T,$(MAX))
# auto-decrementing counter that returns the number of pending targets in base 10
counter = $(words $N)$(eval N := $(wordlist 2,$(words $N),$N))
# BUILD is now defined to show the progress, this also avoids redefining T in loop
BUILD = #echo $(counter) of $(T)
endif
# dummy phony targets
.PHONY: all clean
all: target
#echo done
clean:
#rm -f target *.c
# dummy build rules
target: a.c b.c c.c d.c e.c f.c g.c
#touch $#
$(BUILD)
%.c:
#touch $#
$(BUILD)
All suggestions welcome!
This one is less intrusive and more awesome.
ifneq ($(words $(MAKECMDGOALS)),1)
.DEFAULT_GOAL = all
%:
#$(MAKE) $# --no-print-directory -rRf $(firstword $(MAKEFILE_LIST))
else
ifndef ECHO
T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
-nrRf $(firstword $(MAKEFILE_LIST)) \
ECHO="COUNTTHIS" | grep -c "COUNTTHIS")
N := x
C = $(words $N)$(eval N := x $N)
ECHO = echo "`expr " [\`expr $C '*' 100 / $T\`" : '.*\(....\)$$'`%]"
endif
.PHONY: all clean
all: target
#$(ECHO) All done
clean:
#rm -f target *.c
# #$(ECHO) Clean done
target: a.c b.c c.c d.c e.c
#$(ECHO) Linking $#
#sleep 0.1
#touch $#
%.c:
#$(ECHO) Compiling $#
#sleep 0.1
#touch $#
endif
There wasn't really a question so this is less of a standalone answer and more of an extension to Giovanni Funchai's solution. This question is the first google result for "GNU Make Progress" so I ended up here looking for how to do this.
As pointed out by Rob Wells, the solution doesn't work for <10%, but the technique can be extended with the print formatting done by a helper script in whatever language you feel is portable enough for your build. For example, using a python helper script:
echo_progress.py:
"""
Print makefile progress
"""
import argparse
import math
import sys
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--stepno", type=int, required=True)
parser.add_argument("--nsteps", type=int, required=True)
parser.add_argument("remainder", nargs=argparse.REMAINDER)
args = parser.parse_args()
nchars = int(math.log(args.nsteps, 10)) + 1
fmt_str = "[{:Xd}/{:Xd}]({:6.2f}%)".replace("X", str(nchars))
progress = 100 * args.stepno / args.nsteps
sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress))
for item in args.remainder:
sys.stdout.write(" ")
sys.stdout.write(item)
sys.stdout.write("\n")
if __name__ == "__main__":
main()
And the modified Makefile:
_mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
I := $(patsubst %/,%,$(dir $(_mkfile_path)))
ifneq ($(words $(MAKECMDGOALS)),1)
.DEFAULT_GOAL = all
%:
#$(MAKE) $# --no-print-directory -rRf $(firstword $(MAKEFILE_LIST))
else
ifndef ECHO
T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
-nrRf $(firstword $(MAKEFILE_LIST)) \
ECHO="COUNTTHIS" | grep -c "COUNTTHIS")
N := x
C = $(words $N)$(eval N := x $N)
ECHO = python $(I)/echo_progress.py --stepno=$C --nsteps=$T
endif
.PHONY: all clean
all: target
#$(ECHO) All done
clean:
#rm -f target *.c
# #$(ECHO) Clean done
target: a.c b.c c.c d.c e.c f.c g.c h.c i.c j.c k.c l.c m.c n.c o.c p.c q.c \
r.c s.c t.c u.c v.c w.c x.c y.c z.c
#$(ECHO) Linking $#
#sleep 0.01
#touch $#
%.c:
#$(ECHO) Compiling $#
#sleep 0.01
#touch $#
endif
yields:
$ make
[ 1/28]( 3.57%) Compiling a.c
[ 2/28]( 7.14%) Compiling b.c
[ 3/28]( 10.71%) Compiling c.c
[ 4/28]( 14.29%) Compiling d.c
[ 5/28]( 17.86%) Compiling e.c
[ 6/28]( 21.43%) Compiling f.c
[ 7/28]( 25.00%) Compiling g.c
[ 8/28]( 28.57%) Compiling h.c
[ 9/28]( 32.14%) Compiling i.c
[10/28]( 35.71%) Compiling j.c
[11/28]( 39.29%) Compiling k.c
[12/28]( 42.86%) Compiling l.c
[13/28]( 46.43%) Compiling m.c
[14/28]( 50.00%) Compiling n.c
[15/28]( 53.57%) Compiling o.c
[16/28]( 57.14%) Compiling p.c
[17/28]( 60.71%) Compiling q.c
[18/28]( 64.29%) Compiling r.c
[19/28]( 67.86%) Compiling s.c
[20/28]( 71.43%) Compiling t.c
[21/28]( 75.00%) Compiling u.c
[22/28]( 78.57%) Compiling v.c
[23/28]( 82.14%) Compiling w.c
[24/28]( 85.71%) Compiling x.c
[25/28]( 89.29%) Compiling y.c
[26/28]( 92.86%) Compiling z.c
[27/28]( 96.43%) Linking target
[28/28](100.00%) All done
One could even print a fancy progress bar with unicode characters.
Modified echo_progress.py:
"""
Print makefile progress
"""
import argparse
import math
import sys
def get_progress_bar(numchars, fraction=None, percent=None):
"""
Return a high resolution unicode progress bar
"""
if percent is not None:
fraction = percent / 100.0
if fraction >= 1.0:
return "█" * numchars
blocks = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"]
length_in_chars = fraction * numchars
n_full = int(length_in_chars)
i_partial = int(8 * (length_in_chars - n_full))
n_empty = max(numchars - n_full - 1, 0)
return ("█" * n_full) + blocks[i_partial] + (" " * n_empty)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--stepno", type=int, required=True)
parser.add_argument("--nsteps", type=int, required=True)
parser.add_argument("remainder", nargs=argparse.REMAINDER)
args = parser.parse_args()
nchars = int(math.log(args.nsteps, 10)) + 1
fmt_str = "\r[{:Xd}/{:Xd}]({:6.2f}%) ".replace("X", str(nchars))
progress = 100 * args.stepno / args.nsteps
sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress))
sys.stdout.write(get_progress_bar(20, percent=progress))
remainder_str = " ".join(args.remainder)
sys.stdout.write(" {:20s}".format(remainder_str[:20]))
if args.stepno == args.nsteps:
sys.stdout.write("\n")
if __name__ == "__main__":
main()
Which would result in something like this:
$ make clean && make
[12/28]( 42.86%) ███████▊ Compiling k.c
during progress and:
$ make clean && make
[28/28](100.00%) ████████████████████ All done
upon completion.
This is a slight modification to #GiovanniFunchal's excellent answer.
So I wanted to understand this better and make it work for < 10% so I dug into the documentation and learned more about expr.
# PLACE AT THE TOP OF YOUR MAKEFILE
#---------------------------------
# Progress bar defs
#--------------------------------
# words = count the number of words
ifneq ($(words $(MAKECMDGOALS)),1) # if no argument was given to make...
.DEFAULT_GOAL = all # set the default goal to all
# http://www.gnu.org/software/make/manual/make.html
# $# = target name
# %: = last resort recipe
# --no-print-directory = don't print enter/leave messages for each output grouping
# MAKEFILE_LIST = has a list of all the parsed Makefiles that can be found *.mk, Makefile, etc
# -n = dry run, just print the recipes
# -r = no builtin rules, disables implicit rules
# -R = no builtin variables, disables implicit variables
# -f = specify the name of the Makefile
%: # define a last resort default rule
#$(MAKE) $# --no-print-directory -rRf $(firstword $(MAKEFILE_LIST)) # recursive make call,
else
ifndef ECHO
# execute a dry run of make, defining echo beforehand, and count all the instances of "COUNTTHIS"
T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
-nrRf $(firstword $(MAKEFILE_LIST)) \
ECHO="COUNTTHIS" | grep -c "COUNTTHIS")
# eval = evaluate the text and read the results as makefile commands
N := x
# Recursively expand C for each instance of ECHO to count more x's
C = $(words $N)$(eval N := x $N)
# Multipy the count of x's by 100, and divide by the count of "COUNTTHIS"
# Followed by a percent sign
# And wrap it all in square brackets
ECHO = echo -ne "\r [`expr $C '*' 100 / $T`%]"
endif
#------------------
# end progress bar
#------------------
# REST OF YOUR MAKEFILE HERE
#----- Progressbar endif at end Makefile
endif
I got rid of the : '.*\(....\)$$' part. It would return the last 4 characters of the inner expr command, but would fail if it was less than 4. And now it works for sub 10%!
And here is the comment free version:
ifneq ($(words $(MAKECMDGOALS)),1) # if no argument was given to make...
.DEFAULT_GOAL = all # set the default goal to all
%: # define a last resort default rule
#$(MAKE) $# --no-print-directory -rRf $(firstword $(MAKEFILE_LIST)) # recursive make call,
else
ifndef ECHO
T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
-nrRf $(firstword $(MAKEFILE_LIST)) \
ECHO="COUNTTHIS" | grep -c "COUNTTHIS")
N := x
C = $(words $N)$(eval N := x $N)
ECHO = echo -ne "\r [`expr $C '*' 100 / $T`%]"
endif
# ...
endif
Hope that helps.
Many thanks to #Giovanni Funchal and #phyatt for the question and answers!
I just simplified it even more for my own better understanding.
ifndef ECHO
HIT_TOTAL != ${MAKE} ${MAKECMDGOALS} --dry-run ECHO="HIT_MARK" | grep -c "HIT_MARK"
HIT_COUNT = $(eval HIT_N != expr ${HIT_N} + 1)${HIT_N}
ECHO = echo "[`expr ${HIT_COUNT} '*' 100 / ${HIT_TOTAL}`%]"
endif
!= assigns from shell command
= evaluates variable each time it's used
eval executes its argument without any output
expr allows to make arithmetic calculations
( Not sure though which approach is faster: to call shell with expr or to count 'x'-es with make. )
Usage is the same:
target:
#$(ECHO) $#
Nice trick! (-:
But not really scalable for growing projects that are distributed across many directories with lots of makefiles.
I'd be more inclined to have logging sprinkled through the [Mm]akefiles* in your project and use that to keep track of progress.
Just a thought. BTW Thanks for sharing this.
Edit: Just had a thought. This could be useful in a modified form to display a throbber to show progress while a long task proceeds, e.g unpacking a large distribution tarball instead of just specifying the -v option to the tar command. Still a bit of sugar coating but a bit of fun aswell. (-:
cheers,
Rob