On 1/11/20 5:10 PM, Ian Harvey wrote:
> On 2020-01-10 16:33, Ron Shepard wrote:
>> On 1/9/20 3:37 PM, Ian Harvey wrote:
>>> On 2020-01-09 17:51, Ron Shepard wrote:
>> [...]
>>>> 2) The inability to read an alloctable array of unknown size; that
>>>> should just work in the obvious way.
>>>
>>> The obvious way is perhaps not that obvious.
>>
>> You have an array, and you want to allocate and fill it with whatever
>> is the input data. I know how to do this by hand, but it requires
>> extra memory allocations and transfers. The i/o library knows already
>> how to do all of that without the extra work. It is already doing the
>> hard work, but then hiding it from you. Just make that capability
>> available directly to the programmer.
>
> When you do it by hand, you implicitly make choices about the
> implementation. I don't consider all those choices obvious.
Of course, all implementations of a feature make such choices, but they
can still conform to the programmer interface, whatever that is. I am
not suggesting what that interface is, I'm just advocating for the
underlying functionality.
And just because some feature is implemented as part of the language
does not preclude the programmer from writing something better for
special cases. For an unrelated example of this, a programmer might use
both matmul() and dgemm() to do matrix-matrix products. There is nothing
wrong with that.
> I think that when you start down the path putting together the specific
> list of requirements and behaviours for this sort of feature, it ends up
> getting rather messy. Messy does not mean impossible, but it might be
> undesirable.
>
> I have an input file connected to unit `unit`.
>
> 1, 2, 3, 4
>
> I have an I/O statement, where a and b are integer, allocatable rank one
> arrays.
>
> READ (unit, *) a, b
That seems ambiguous to me, and to you, no doubt, which is why you are
using it in an argument against implementing this functionality. What
I'm requesting could just as well be implemented as
call read_and_allocate( unit, a )
in which case there is no such ambiguity. You would expect the
allocatable array a(:) to end up being allocated with 4 elements and
filled with the obvious data. Perhaps optional arguments could be added
to set the lower bounds, perhaps formats in addition to list directed,
and of course it should be generic for any data type.
> What happens? Be specific. Consider all the variations. Be specific.
> Now try and do that without having many pages of complicated
> requirements with lots of inscrutable special cases.
You are looking for excuses to not implement the feature rather than
looking for ways to do so.
>>>> 3) Perform i/o on a structure with an allocatable component without
>>>> jumping through hoops; I think that operation also should just work
>>>> in the obvious way.
>>>
>>> And that obvious way is...?
>>
>> There is a well-defined way when the components are not allocatable.
>> Reading and writing allocatable components in the same way seems
>> obvious enough.
>>
>> I'm assuming here on reading that all of the components have been
>> allocated prior to the i/o operation.
>
> That last requirement certainly wasn't obvious to me.
>
> In all other places within the language, the value of an object of
> derived type includes the "allocation status, ... dynamic type and type
> parameters, bounds and value" ... of its components.
My request is for something simpler than all that. Suppose you have a
derived types
type xxx
integer :: a(4)
end type xxx
type yyy
integer, allocatable :: a(:)
end type yyy
type(xxx) :: x
type(yyy) :: y
allocate( y%a(4) )
From this point on, I think you should be able to use the derived types
x and y in pretty much the same way, including such things as
read(unit,*) x
read(unit,*) y
That's it. nothing deep and profound, just treat allocatable components
like nonallocatable ones when it is appropriate. This already applies to
assignments and expressions with array operators, but for some reason
not with i/o statements with derived types.
[...]
> Out of the blocks - you are saying it should work one way, I'm saying it
> should work some other way, and I think that is very representative of
> the situation, so rather than pick one or the other the standard just
> says "here are some tools (UDDTIO), but otherwise sort it out yourselves".
UDDTIO was slow to be implemented, so I do not have much experience with
it. But from what I've read, it seems the same as writing your own
subroutine to write out the data, and then invoking it through a
read/write statement rather than a call statement.
Is that right, is it just a thin layer of syntactic sugar?
In any case, I'm not suggesting that UDDTIO be removed from the
language, but rather than the obvious default behavior be invoked when
doing i/o on derived types. When you delete an old component or add a
new component to a derived type, then the obvious thing should happen
when a read/write is executed, you should not need to dig into a
user-level subprogram and manually synchronize with the new definition.
If you want to do something that is not obvious, then UDDTIO is there
for that.
>>>> 4) The ability to query the environment at runtime to determine the
>>>> total allocatable memory usage. This would allow the programmer to
>>>> choose algorithms, or to adjust parameters within an algorithm, in
>>>> order to optimize the execution.
>>>
>>> It would be easy enough for a runtime to track and report this (you
>>> can track it now using Fortran source, for source that you have
>>> control over), but there might not be that much of a link between the
>>> use of memory by allocatable objects and the total use of memory of a
>>> program. The necessary accounting will also cause a minor performance
>>> hit, particularly in the face of things like threaded or multi-image
>>> execution, depending on the precise definition of the thing you want
>>> reportable.
There should be no extra overhead because everything that I'm requesting
is already computed anyway. I'm just advocating that it be made visible
to the programmer through a standard interface.
As for doing it manually, yes you can do that for allocatable arrays.
Every time you allocate, deallocate, or allocate-on-assignment for such
an array, you could call a user-level subprogram to add or subtract from
the total. That would be a major hassle, to say the least, and as I said
above it would be redundant with what the runtime libraries are already
doing.
But the more serious problem is that there are other memory allocations
done by the compiler that you have no direct control over. So if you are
trying to avoid hitting some memory usage limit, keeping track of just a
subset of the memory usage would not solve that problem. The compiler
generates array temporaries in many places, in expressions, in order to
optimize common subexpressions over several statements, and during
subprogram calls. Sometimes the way this is done depends on the compiler
optimization level. The only real way to monitor the memory usage is to
query the runtime libraries that are doing all that.
>> Whatever it is that causes your program to crash (stack overflow, swap
>> space limit, heap overflow, batch queue quota limit, etc.), that's
>> what needs to be available to the programmer. This information is
>> usually available in a machine-specific nonportable way, just
>> standardize an API to an intrinsic subprogram and make it portable.
>
> I assumed by "allocatable memory" you were talking about memory used by
> objects within the Fortran program that have the ALLOCATABLE attribute.
> This is a retrospective measurement - "what has been allocated".
Yes, of course you also need to query to see what limits are in effect
so that you can tune your future allocations, or select the appropriate
algorithms, in order to fit within the constraints.
$.02 -Ron Shepard