🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.lang.misc
Article #6117

Re: Dynamic array dimensioning

#6117
From: David Keppel
Date: Sat, 07 Jan 1989 19:53
129 lines
5294 bytes
>In article <117400002@uxa.cso.uiuc.edu> gsg0384@uxa.cso.uiuc.edu writes:
>>[What languages have run-time array sizing in a particular module?]

In article <827@laura.UUCP> hmm@laura.UUCP (Hans-Martin Mosner) writes:
>These languages are quite unrelated to Fortran, but you asked for it:
>Smalltalk, most (all ?) Lisps, some Basics & Pascals, C (if you accept a
>pointer for an array and implement growing by copying)
>[...]
>[Summary: only added cost is an extra indirection]

I'd like to belabor a particular point, namely the difference between
array sizing at procedure invocation, and dynamic array sizing.

In dynamic sizing, a statement "a[i]:=val;" is meaningful for all
values of i (for which there is sufficient memory); the array "a" is
grown if necessary.  In arrays that are sized at procedure invocation,
the array has some fixed upper/lower bound for the duration of the
procedure call.

The first is certainly more flexible.  It is also more expensive, and
for several reasons.  In addition, *both* are (in general) less
efficient than statically-sized (even stack-allocated) arrays since it
is not generally possible to do as much constant folding or remove as
much computation via loop induction.  It is also usually necessary to
keep both a stack pointer and a frame pointer.


Here is a case to ponder:

    functin foo(x:integer);
	s[1..x], t[1..x] : character;
	i integer;

	for i := 1 to x do
	    ... use s[i] & t[i] ...
    }

In function-invocation sizing, the optimizer can determine (at compile
time) that no reference into s or t will ever be outside the range
0..x-1, so no bounds checking is needed.  Also, if the function call
frame looks like:

	+-----------+
	| call stuff|  <- fp
	+-----------+
	| i storage |
	+-----------+
	| s storage |
	|     :     |
	+-----------+
	| t storage |
	|     :     |
	+-----------+
			<- sp

Then access to "i" is always by a constant offset off of the stack
pointer

	movl	fp+4,r0

Access in to s is similarly cheap -- as cheap as a statically-sized
array:

	movl	(fp+4)[r0],r1

Since the size of s and t are bound at runtime, the location of t[i]
is a runtime value relative to both of fp and sp and must be computed
dynamically.  The cheapest (general) way to do this is keep a pointer
to the storage for t.  This pointer lives at a constant offset from
the frame pointer.

	+-----------+
	| call stuff|  <- fp
	+-----------+
	| i storage |
	+-----------+
	| tp storage|
	+-----------+
	| s storage |
	|     :     |

				; assume r0 holds "i"
	mov	(fp+8),r1	; r1 <- pointer to t storage
	mov	r1[r0],r1	; r1 <- t[i]

Thus reference in to an arbitrary array location takes an extra
reference.  [Although the "for" loop may be coded to keep track of
both "i" and "x-i" and thus keep track of t[i] as an offset from "sp",
this only works for two arrays and so does not generalize for the
worst-case cost for access.]

If we are stepping through the array, as in a "for" loop, we can
optimize accesses in memory references by, say, loading the pointer
"tp" into a register.  In certain cases, we need not even allocate "tp"
on the stack, we keep it in a register and/or recompute it on demand.

In the above description, the procedure must have both a frame pointer
and a stack pointer.  This adds an extra few instructions to
call/return, but only for those functions with variable-sized arrays
(probably acceptable).  For the extra price of copying the call frame
(and arguments), we can get rid of the frame pointer.


If the arrays are *dynamically* sized, we lose several things:

* It is impossible to perform loop induction to remove overhead on
  dynamically-sized arrays.  A big problem here is that we must check
  for overflow on every array access.  This is typically a lot of
  extra overhead, a significant portion of the runtime of a given
  function.
* The storage can be allocated on the stack only when there is only
  one dynamically-sized array in a given procedure.  Thus procudure
  entry/exit will generally be expensive (calls a memory allocator)
  even if the array is never resized.
* It's harder to get rid of the frame pointer.
* It is not as often possible to allocate a dynamically-sized array
  on the stack.

As a language designer, one choice is to make all three mechanisms
available and choose the most efficient implementation for each.
Those who rely on more general models will pay the price (code size,
runtime), while those who do not need it will not pay for it.

I hope that this is helpful.

  ;-D on  ( Now if I can just get rid of the array, it'll be *fast* )  Pardo
-- 
		    pardo@cs.washington.edu
    {rutgers,cornell,ucsd,ubc-cs,tektronix}!uw-beaver!june!pardo
Technical Details
Message-ID: <6888@june.cs.washington.edu>
Path: arc1-ca.pugleaf.net!utzoo!attcan!uunet!lll-winken!ames!mailrus!cornell!uw-beaver!uw-june!pardo
References: <117400002@uxa.cso.uiuc.edu> <827@laura.UUCP>

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!utzoo!attcan!uunet!lll-winken!ames!mailrus!cornell!uw-beaver!uw-june!pardo
From: pardo@june.cs.washington.edu (David Keppel)
Newsgroups: comp.lang.misc
Subject: Re: Dynamic array dimensioning
Summary: long, specific example
Message-ID: <6888@june.cs.washington.edu>
Date: 7 Jan 89 19:53:30 GMT
References: <117400002@uxa.cso.uiuc.edu> <827@laura.UUCP>
Reply-To: pardo@cs.washington.edu (David Keppel)
Organization: U of Washington, Computer Science, Seattle
Lines: 129