Save attribute in type declaration

Hi!

I am wondering how this is interpreted by Fortran compilers (they seem to accept it):

module savemodule
  implicit none

  type :: savetype
    integer :: savevariable = 1
  end type savetype

end module savemodule

use savemodule
implicit none

type(savetype) :: s1, s2

s1 % savevariable = 2
s2 % savevariable = 2

print *, s1 % savevariable, s2 % savevariable

end

If on the other hand I add a save attribute to

    integer, save :: savevariable = 1

Compilers start to complain that this is not allowed. However, I believe save is implicitly added to the original version too. What is more strange is that with the “implicit save” attribute the code performs worse. There is a code that had initialization this way and the performance is much worse in loop with overloaded multiplication and sum of a derived type. When the initialization was moved to main program statements, the code was as performant as the comparable code with real numbers (compiled with gfortran). So clearly save is doing something in the type declaration.

And the compiler is right. There’s a fundamental difference between the initialization of a variable and the initialization of a component in a derived type: in the former case this is an initialization at compile time, once for the whole execution of the program, and in the latter case this is an initialization at runtime, each time an instance of the derived type is created.

Nope. The save attribute has actually no sense for a derived type component, as nothing is instantiated at this point, this is just the description of what the type is.

One would need to see the code to understand and possibly explain what really happens.

Many thanks for the answer @PierU.

I thought first that save doesn’t make sense in type declaration statement. But I got a bit derailed because of

  1. my observation of initialization of variables upon declaration statements of derived type. Btw. When there are more variables with initialization, the worse performance gets (compared to a similar situation with initializations at main program). I should mention that link-time optimization was used (-fwhole-file).
  2. Implicit save in variable declaration when initialized

So i started thinking that there might be some special meaning when initialization is used at type declaration statements.

I guess I am trying to understand the difference between initializing a variable at the derived type declaration statements (the first code I posted) and initialization only at the main program:

module savemodule
  implicit none

  type :: savetype
    integer :: savevariable
  end type savetype

end module savemodule

use savemodule
implicit none

type(savetype) :: s1, s2

s1 % savevariable = 2
s2 % savevariable = 2

print *, s1 % savevariable, s2 % savevariable

end

When you specify an initial value for a component, that is called default initialization. As others have explained, nothing is actually initialized at that time because there is no variable of that type to initialize. It just tells the compiler that when variables are declared of that type elsewhere, those will be the initial values. The main program has nothing to do with this, the same thing happens everywhere.

If you want to initialize your variables upon declaration, then you would do something like

type(savetype), save :: s1=savetype(2), s2=savetype(2)

The save attribute there is the default for initialized variables, but I usually specify it anyway to tell human readers of the code what is happening. I think most programmers believe it was a mistake for fortran to make that the default, and I don’t want to appear to support that bad choice.

If you have both default initialization and actual initialization, as above, then the actual initialization overrides the default initialization.

[edit] I wanted to add that when the values are set in assignment statements, that is not initialization in the fortran sense. Those are just normal assignments. It would be difficult to say exactly which parts are done at compile time and which at run time for this code. It is simple enough that the whole thing could be reduced to just the equivalent of

print *, 2, 2

with all the variations that have been discussed. A more interesting set of questions is what happens in the various cases when the print statement is in a subroutine that is compiled separately from its calling program. Then you can ask questions about when the value is set, if the variable is saved between calls, or if the value is reset anew each call.

1 Like

Thank you for the very informative answer!

I guess @PierU is right, we need to provide the real example such that people can explain the issue with our code.

The thing is that it is a bit long, so we will try to provide some shortened version that still captures the issue.

I think the mystery is solved: Thanks to the responses here I started thinking that the default initialization takes place in our code each time the overloaded binary operation is executed. And if that is in a for loop, it happens every time. Consider the following:

do i=1,n
  a(i) = b(i)*c(i)
end do

In case b and c are vectors of a derived type and the (*) overload instanciates a derived type that has some default initializations, then that happens on every loop cycle. Thus it performs worse than a deriverd type without default initializations. Moreover, a derived type with no default initializations should perform equally well compared to real type vectors if the overloaded ultimate multiplied types are real. Given that compiler knows how to optimize that.

Let me know what you think @PierU @RonShepard. Thanks!

2 Likes

This sounds plausible. You might be able to tell if your conjecture is correct by examining the intermediate code (or the assembler code) output for the two cases, with and without initialization. With gfortran, try -fdump-parse-tree. I would guess that simply initializing an integer component to a value within existing memory would not affect performance much. But if a derived type temporary is allocated anew for each pass through the loop for the initialization case, that could affect performance.

1 Like