Enabling/disabling debug output

In the thread Speed of Writing to File vs /dev/null it is suggested to use preprocessing to ensure the debug output that you do not need does not hamper optimisation. While I cannot be sure that the technique I present here is flawless, it is simpler to use than a preprocessing step - it is pure Fortran. See the attached program.

The technique is simple:

  • Define a small module which defines a logical parameter, debug.
  • Use this module and this parameter wherever you want to add debug output:
        if ( debug ) then
            write(*,*) "whatever you want to write"
        endif

If the value of debug is false, then no machine code at all is generated by the write statements. I checked it using Intel Fortran oneAPI with the options “-O3 -Qvec -Qopt-report”, which produce a report on the optimisation and vectorisation of the code (and especially why some things were NOT vectorised) and gfortran with the option “-S” to produce assembly code.

With Intel Fortran oneAPI, you get a clear report about the vectorisation of the loop depending on the value of debug. With gfortran you get different assembly code, depending on that parameter. When I commented out the if-block altogether, I got exactly the same assembly code as with debug set to .false.

This empirical evidence is merely an indication of course, but I think the technique is worth considering for actual use.

optim.f90 (1.1 KB)

1 Like

This type of solution is fine, better that the preprocessor step I would say, but still has 2 issues:

  1. it requires touching everywhere the writes happen, which could be thousands-tens thousands-more places
  2. like the preprocessor, code has to be recompiled to change debug on/off

With something tied to actually opening the file (which does not seem to be possible with current Fortran), these 2 become benefits instead:

  1. the debug file is likely opened once, at the start of the program, so only a single area needs updating to the new debug on/off method
  2. it is trivially easy to have some input to the code determine which branch to take (debug on/off)

I’ve used something like this for several years. However, I wrap the definition of the debug parameter in preprocessor logic so I can turn on debuging without touching the code explicitly ie

#ifdef DBG
     logical, parameter :: debug = .true.
#else
     logical, parameter :: debug = .false.
#endif

To turn on debugging you just add -DDBG to your compiler options. This limits the scope where the preprocessor touches your code to just one file.

1 Like

If the debug flag is an implicit or explicit constant the compiler is free to identify that an “if(debug)” is dead code and eliminate it; and subsequently proceed to fully optimize the remaining code. If the debug flag is public and the module is compiled seperately or other things prevent that optimization you should see differences. Usually asking for no optimization would leave it there; but that would mean you are probably debugging anyway so tht is not a serious issue. Preprocessing is always a problem, primarily because it has never been standardized so alternatives are good; and not all codes need heavy optimization so some of the other methods are OK when performance is not a driving force in the code development so that seems fine as long as you mention DEBUG has to be identified as a constant.

Use of debuggers and profilers are also very useful.

Most of my procedures are pure so writing debugging information is quite problematic without preprocessors epecially for nested procedures. For writing anything I have to remove pure from the current procedure and from all those that call it, resulting into a cascade of changes. With preprocessor pure keyword can be made conditional.

1 Like

One nice feature of this approach, relative to preprocessor approaches, is that the compiler usually does syntax checking within the if-block, even if the flag is .false.; with the preprocessor, the compiler never really sees the block of code. As to whether the dead code is eliminated, that is not guaranteed by the standard, and in practice it likely depends on optimization levels and compiler option settings. Also, as noted elsewhere, debug must be declared as a PARAMETER. If it is just a variable initialized to .false., then the compiler will likely leave the code in since it may not be able to determine if it is redefined somewhere (including by an interactive debugger).

I’m using this technique heavily in my codes.
The fact that you can can control the debugging for a single function/subroutine or a module is very useful because you can turn on and off only the part of the code you want to debug. If you want to debug the whole code, you can always define a global variable debug (known everywhere in the code):

        if ( debug_local .or. debug_global) then
            write(*,*) "whatever you want to write"
        end if

With that, you can turn on/off the debugging globally and/or locally.

Also, I’ve noticed a difference when debug=.false. between ifort and gfortran. At the linking step, the calling functions or subroutines are not resolved with gfrotran while they are with ifort.

1 Like

Hm, Fortran does not define a preprocessor in the standard, but you could use an include file instead :slight_smile: - something along these lines:

include "pure.inc"
function f( ... )

with the file “pure.inc” looking like:

pure &

(or simply empty)

Not ideal, but in this way you would use Fortran only.

A limitation of interactive debuggers, unfortunately, is that sometimes the error occurs in the Nth iteration of a loop, where N is any number exceeding your patience. Then I definitely use the venerable technique of writing intermediate results until I am sure I know when to invoke the deubgger usefully.

But then can you use conditional if outside of the function? Preprocessor conditionals are used in modules, outside any procedure, but can you write:

module some_module
contains
if(debug) include "pure.inc"
function f(...)
...
end function f
end module some_module

?

No, I was thinking more of replacing the contents of the include file. The capabilities are very limited, indeed.

1 Like

What I think is required is to have something on the lines of error stop messages, but without halting the program.

Another solution is to define a debug_info type and pass it all around, gathering all the debug info and printing it from outer/main procedures. Definition of debug type will be very much problem dependent.

For example,

type :: debug_info
   integer :: i
   real :: r
end type debug_info

pure function f(...,di)
   type(debug_info),intent(inout) :: di

   if(debug) di%i = some useful info
end function f

Hopefully, the compiler will ignore the unused argument if debug=.FALSE.

One can simply write to error_unit from the iso_fortran_env module, and then continue on.

This is a minor problem when using preprocessor directives. In this case, there are sometimes variables that are referenced only within the debug blocks, so when the blocks are turned off, the compiler then thinks there are unreferenced dummy arguments or unreferenced local variables and prints the appropriate warnings. If the programmer turns off those warnings, then he risks being uninformed about other unused variables that he might want to eliminate from the code.

Yes. I tried using BLOCK statements and in some cases that works well, but if the variables are needed in more than one region of the procedures it becomes no better than just putting the declarations at the top of the procedure. The declarations can remain in the top declarations area and also be conditionally compiled but that because an ugly bookkeeping problem except for simple cases. All that being said, if your debug statements are all in a nice distinct section a BLOCK is quite natural. So in some cases something like this where iostat and iomsg are not used except for the debug code works

#ifdef DEBUG_VERSION
DEBUG: block
integer :: iostat
character(len=256) :: iomsg
   if(msg_level>0)then
      write(stderr,*,iostat=iostat,iomsg=iomsg) a,b,c
      if(iostat /= 0)write(stderr,'(a)')trim_iomsg)
   endif
endblock DEBUG
#endif
1 Like