          ClariON-TIME Conference - Johannesburg, July 28/29 1994

                  David Bayliss - Squeezing the Last Drop

(Transcribed by Rob Mousley 100075,772 who accepts no responsibility for errors!  The ClariON-TIME
conference was the third South African Developers Conference.  David Bayliss and Barry Lynch (MD,
Clarion Europe) were guests of honour.)


Two phrases seem to permeate computer science:  one of them is that hardware is getting faster and faster
and faster and we don't need to worry about the efficiency of our applications.  I normally get that from
professors, R&D managers and  from MIS (mis-?) managers.  The other phrase I get from users and that
runs 'how come my application is so bloody slow?!'

Machines are getting faster but applications are getting more and more complicated.  I had a ZX81 (based
on a Z80 chip, with 1K of RAM and a tape, which was the backing store) and to print out a one page letter
on that took 45 seconds.  I now have an 8MByte 486DX50 with 300 Mbytes of hard disk and a nice laser
printer and it takes 45 seconds to print a one page letter!  Applications aren't getting any faster, they're just
getting more complicated.  And the difference between an application that looks ok, and an application that
looks well programmed is still how snappy it is.  If you can make your application look snappy when
someone else's 'gets there in the end' people will assume that you're a better programmer.  

What I'm trying to do here is encourage more efficient Clarion applications.

The way I'm going to do this is to increase your knowledge of the Clarion language.  That doesn't
necessarily mean hand-coding, it just means things like understanding the data types which are underneath
your applications.  The reason for this and for not doing 101 'Tips and Techniques' is that by understanding
what you're doing you can,  without thinking about it, make your applications run twice as fast and be half
as big as they used to be.  If you do it all badly and then go through trying to apply tips and techniques you'll
have a system which is not just slow, but that you don't understand either.  So I'm going to try to use this
session to increase the understanding of what the Clarion system is.  I'm also going to be pointing out some
of the unusual features of the clarion language which you may not know about if you come from Basic, C
or Clipper and which can actually make Clarion programs run substantially faster that other programs.

I'm doing this two main levels.  One is the data level - you can make a big difference to your application just
by choosing the right  data types.  Now that's really good news, because even if you're strictly a Designer
user you don't even use source code insertion points, by picking the right fields in your data dictionary and
making appropriate choices you can easily halve the size of your application and make it run twice as fast. 
But then I will actually be going down to the coding level, not necessarily  if you write all of your stuff in code
but if you use source code insertion points or filters extensively, I'm going to be showing you those parts of
the Clarion language that can do you some good.

Data Placement

Before I'm going to go on to what is the right type of data, one of the first decisions is where you put it.  You
have a number of choices here.  The traditional Clarion place to  put global data is in your main program
unit.  Providing you have less than 64K of data in your entire application, the most efficient place to have
it is globally.  Everybody can get at it extremely cheaply and there is no overhead.  If you have more than
64K of data in your main program segment, I can still cope with that but there will be an overhead involved
in accessing each item of data.  I should specify here that I'm from R&D.  If I say something is slow, what
I mean is 5 or 6 times faster than Clipper, I don't mean slow in any marketing sense, I just mean slower than
you can get if you know what you're doing.  The next  type of static data you have is module specific.  I went
over this  in the protected mode talk.  Basically the idea is that in any one source unit you can have  data
that is specific to that source unit.  Accessing data from your own module is also very fast.  The advantage
of putting it module wide instead of globally is that if you change the type or add more you don't have to re-
compile your whole application.  Also, if you can use exclusively module data, then that will be fast because
I don't have to change segments.  Module data really comes into its own in a protected mode system.  In
a protected mode system, accessing data from different data segments is quite slow.   That's just to say,
some of you may have programmed in Modular-2 - you can do the M2 type data referencing.  What does
that mean?  Well in C, if you have an external variable the compiler doesn't know where the hell it is so its
very slow to access it.  In M2, on the other hand, providing the compiler knows where the data comes from
ie it knows it comes from the main program unit or it comes locally it will be accessed quite quickly.  If
however you have the ,EXTERNAL attribute it just knows that it is somewhere and that it has to use the C
mechanism.  So accessing external variables is slower than accessing global or module-specific variables.

Your next choice is local data.  Providing you haven't used the STATIC attribute, local data is allocated from
your stack frame in CDD, in CW its in the stack frame if its less than a certain size, otherwise its allocated
from the heap.  If its allocated from the stack frame its very cheap to get at.  If its allocated from the heap,
it is slightly slower to get at.  You have a pragma called the STACK_THRESHOLD pragma and you set that
to a number like, say 5K and if your GROUP or your STRING is bigger than 5K then it will be from the heap,
if its less than that it'll be local.  The nice thing about local data is that it doesn't add to the size of your
executable and it doesn't add to the total memory consumed by your application.  So if you're using overlay
model, the more data you can put into local data, the less you'll get this effect of the program slowing down
over time.  
In CW you now have this concept of multi-threading.  Now, the way to explain that is that you can have the
same procedure executing twice at the same time.  Now, local data will be unique to that procedure so you
don't have to worry about it.  Now with global data, you have a problem because if you have two procedures
executing at the same time, accessing the same item of global data, they can be corrupting each other's
copies.  So we have something called the THREAD attribute.  If you put this on an item of static data, each
procedure gets its own copy of that global item of data.  So its nice and convenient.  Thread variables are
easy to access but there is an overhead on each thread switch.  What do I mean by that?  If you have
hundreds and hundreds of thread attributes throughout your application, when you click from one window
to another, there will be a noticeable pause.  

Initialisation

A very nice feature of Clarion is that you don't need to have uninitialised variable problems.  The Clarion
compiler will ensure that any variable that comes into scope, including local data, is actually cleared for you. 
So if you have a group that contains a string and a number, which is local, by the time you get to the CODE
statement in your procedure, your string will be blank and your number will be zero.  That's the default
behaviour if you've just said, for example, 

MyVariable     LONG

You can, additionally to that, initialise a variable to a given value so you can initialise a string to 'Hello world'
or a number to 79 or something.  The code that the compiler generates is almost exactly the same as if you
had done the assignments yourself.  What I'm basically saying is that there is an overhead on procedure
entry to having these variables initialised.  If you don't take any advantage of the fact that your variables are
initialised, so you assume that you will be assigning something into them, you can use the AUTO attribute
(and you can do this in Generator now) on that variable  and you just remove that initialisation overhead on
the way into the procedure. 

Mixed Groups

As I've said, you can have things initialised to blank or you can have them initialised to a value.  In a GROUP
you can obviously have some fields which are cleared, and some fields which are initialised to a value.  This
is generally a bad idea.  You should either have the whole group cleared, and then just assign one or two
data items across yourself, or if its a group where all the items are initialised that's ok.  But if its some
mixture of that then you're wasting your time because the compiler will clear the structure and then it will
assign across the data items for you.  


Now, here we hit the really significant stuff.

Believe it or not, there are only three data types in Clarion, the rest of them are data storage types.  What
do I mean by that?  If you have any expression, by which I mean addition, comparison, assignment, before
that happens, your data storage type will be converted into one of three main data types.  These are LONG,
REAL and STRING.  Now why do we do that?  Well Clarion has this rather nave notion that numeric
expressions ought to give the right result.  Very few other languages have this idea so in C if you multiply
together two USHORTS, and subtract a third USHORT if you get unlucky, it will give you the wrong answer. 
Clarion doesn't do that, it performs all integer computations at LONG width, and all other numeric
computations at REAL width.  So Clarion will always give you the right answer.  But to do that, it means the
compiler is doing a lot of work behind the scenes that you don't necessarily see.  One that particularly fools
a lot of people, which is why I've got it up there, is that the ULONG data type, when used in arithmetic, is
treated as a REAL.  So if you are using a ULONG as a LOOP variable, it will be using floating point
instructions to do those increments.  So you really shouldn't do it - it will be very slow on a machine without
a co-processor.

Those data types which are promoted to INTEGER are BYTE, SHORT, USHORT and LONG.  Those which
are promoted to REAL are REAL, SREAL, and ULONG.  

(Tape break...)

Now we have a genuine BCD library which undergirths all DECIMAL arithmetic.  What does that mean? 
It means that DECIMAL arithmetic on a machine without a co-processor is now faster, if you're using a
DECIMAL, than if you're using a REAL, which is quite nice.  Better still, it actually means that it gives you
the right answer.  If you've ever studied numeric analysis, you will never ever use REALs in financial
computations.  

A quick digression.  I worked for an insurance company.  I came out of an academic university and I went
into this insurance company.   I had a look at some of their algorithms, and I said 'Do you know that all of
your pension predictions are completely wrong?!'  And they said 'Oh no, no, we're using REALs - they're
accurate to 17 decimal places, they're right!'  I said 'Yes, but you're subtracting two of them, with big values
in them so in fact your results are only accurate to two significant figures.'  They didn't believe me.  Then
the (?) inspectors came in, and they were using a BCD package, and sure enough their results were only
accurate to two significant figures.  If you subtract two REALs from each other, which are a similar value,
you lose all the precision of your computation.  So for financial mathematics, you really really must use
DECIMALs.  And that is why we've now built a BCD library into Clarion for Windows.  

In addition to that we have also given you 31 digits of precision either side of the decimal point.  So you can
now compute to 1/1000 of a penny quite easily and you don't get any overflow.  There is then some weird
and wonderful stuff.  BFLOAT4, BFLOAT8, TIME, DATE.  These are really there for compatibility with other
data storage formats they should only really be used in file structures.  They're extraordinarily slow and
don't give you any advantages at all.

Type Promotion

As I said, when we perform anything, I'll take addition as an example.  When I'm adding together two
USHORT numbers, I will first promote them to a LONG I will then add them at LONG width and convert
them back to a USHORT.  What that means is, that if you are doing arithmetic in a critical loop, you should
use LONGs, because I then don't have to do any type promotion.  The exception to that is I spotted the case
that a lot of people use BYTEs for boolean flags.  1 means yes its true, 0 means no it isn't, 2 means well
hey I don't know!  So if you're doing a comparison of a BYTE with a number, that is very efficient.  The next
most efficient thing is using LONGs.  Other than that I'm talking about 10, 15% here, but LONGs are the
best thing for computation.  Similarly, if you're doing floating point, REALs are the fastest, DECIMALs are
the most accurate and the fastest in CW.  

Strings

You have three types of strings in CDD and in CW.  STRINGs, CSTRINGs and PSTRINGs.  I heard
somebody say that STRINGs are what you normally use, CTRINGs and PSTRINGs are just for
compatibility.  This is not true.  The three different types of strings have very different properties.  It turns
out if analyse code, that the most time consuming thing is in fact taking the length because before you do
anything else to a string, you normally take its length.  I do even if you don't.  And by 'I do' I mean the
compiler does.  So the important thing is how long it takes to get the length.  Now strings are blank padded,
they can be up to 32K long.  So that means to take the length I scan from the right hand side for the first
non-blank character.  So if you have a buffer which is very big, and have small word in it, it takes a long time
to do a length.  So strings should be used for those cases where your string is quite small and you know
that its generally going to be quite full, because I then won't have to scan far to get the first character.  
CSTRINGs can also be up to 32K long, but they're zero-terminated so I scan from the left when I'm
computing the length.  So they are very good for those situations where you need a massive buffer just in
case, but where normally the string is going to be quite small.  So rule of thumb, these are all just heuristics,
but if you think your string will be over half the size of the buffer, use STRINGs, if you think they will be
generally under half the size of the buffer, use CSTRINGs, both of which can be up to 32K long.

If you're prepared to put up with strings that are only 255 bytes or less, then you can use PSTRINGs which
store a length byte, and those are fastest of all.

String Functions

In CDD version 3, returning a string from a function is a very slow process.  If it can possibly be avoided then
it probably should be.  This has been overcome in CW and it is quite quick to return from a string function. 
Remember that our string functions are string functions as well but we have optimised them so that CLIP(),
INSTRING(), UPPER() and LOWER() are quite fast.  The slow one is the SUB() function.  But we have got
a way around that which some of you may not know about, that all strings are now implicitly character
arrays as well.  So you can get at element 7 of a string just by  saying MyString[7].  And that is very very
efficient - no code is generated for it.  We've  gone one step further in CW where you can say MyString[7:9]
and that is items 7 to 9 of you string.  Again, that generates zero code.   The difference is that you get a
string 'slice'.  SUB() gives a copy of the string, the square bracket notation just gives you a slice out of it. 
But use the slice if you can.

Picture Types

Pictured strings are by far and away one of the most useful of the Clarion data types, but they're also by
far and away one of the slowest.  So you have to use them wisely.  The first thing to note is that all pictures
have an implicit base type.  What do I mean by that?  Well remember that all variables are converted either
to LONG, REAL or STRING before anything happens to them, well for pictures what they're converted to
depends on the picture.  Now if you have an @P picture with 9 or fewer places in it, it is implicitly a LONG. 
@T's and @D's which are times and dates are also implicitly LONG.  What is interesting about that is it
means if you're assigning a REAL number to an @P picture, it will be truncated, it will not be rounded.  

In CW the @N picture gives you an implicit DECIMAL.  In CDD, @N's are implicitly REAL, @E's are always
implicitly REAL and @P's with 10 or more places are implicitly REAL.  The significance of that is that if you
have a machine without a co-processor, REAL computations will go ten times slower than LONG
computations.  If you do have a co-processor the difference is that REAL computations are typically twice
as slow.  But whatever the base type, using a pictured string rather than actually using a genuine variable
will be about ten times slower and generate twice as much code.  So you should use pictured data types
for input and for display and if necessary for compatibility with an existing database, so if a file  structure. 
So they shouldn't really be used for adding numbers and things.  You can do it but, hey, I don't want to be
around when you do so!

@K's and @S's are implicitly STRING, the @K is only available for data entry, its not available in a
FORMAT statement.  There is no reason at all that I can think of for using an @S picture.  You should never
FORMAT to @S, you should just use the LEFT() function.  @S's I think are there just because it 'rounds
it off'.  

Parameters

In Generator this is not an issue because Generator doesn't really use parameters.  But if you're doing
some kind of coding or calling library functions, the most significant decision you can make is what kind of
way, what kind of manner you use for passing  a data type into a procedure.  You have two basic choices. 
You can pass in by value, or by address.  To pass in by value, you just use the data type in the prototype;
to pass in by address you prefix the data type with a star ('*').  For numeric values, you should if possible
always pass in by value.  There is no advantage in passing by address and if you pass in by address the
code in the procedure will run slower than if you pass in by value.  

The complete opposite is true for strings.  If you pass in a STRING by value I have to copy the whole thing
into the procedure on procedure entry.  If you pass it in by address, I don't.  So for STRINGs you should
always pass in by address if you possibly can.  

In both CDD and CW, GROUPs are passed in as if they were STRINGs (just about).  Now what that means
is that you can't get at the fields inside a GROUP.  The difference is that you can CLEAR a GROUP that
has been passed in as a GROUP.  Passing in GROUPs is always done by address.  Whatever you ask me
to do, I do it by address.  So passing in GROUPs is efficient.

There is a particular issue in that you can pass in DECIMALs and DECIMAL arrays by address.  It works,
but it generates quite slow code.  The reason it generates quite slow code is that you can pass any
DECIMAL data type into a procedure that accepts DECIMALs.  So in the procedure, I have to work out what
kind of DECIMAL you passed in, whether its a DECIMAL(10.2) or a DECIMAL(19.7) or something and I
therefore have to do those checks every time I access the variable.  So DECIMALs passed in by address
are slow.

Passing in arrays is fine and I always pass them in by address whether or not you ask me, so it is quite fast
for single dimensional arrays.  Multiple dimensional arrays can be quite slow because I don't know in the
procedure what the size of the data items will be.  The rule here is that if you're data item is some power
of two, that's two bytes, four bytes, eight bytes, then it will be fast.  If you've got a dimensioned array of
GROUPs which are 13 bytes or something horrible then data accessing will be slow.

Strings revisited - in CW we're doing on-going work on STRINGs.  In particular, as I mentioned, we have
already optimised the way in which STRINGs come back, but we're also just completing the way in which
STRINGs are passed in.  So it is now possible to pass in STRING expressions by value and it is actually
done quite efficiently.  The basic rule there is try not to nest STRING calls if you possibly can.  If you do nest
them then you should always but the CLIP in as far as you possibly can because everything else is then
working on the shortest possible data string.  So if you've got a STRING which you're going to CLIP and
you're going to UPPER it, CLIP it on the inside and UPPER it on the outside, ie

     UPPER(CLIP(MyString))

Ommitable Parameters

These should be avoided completely.  

Entity Parameters

Pretty much unique to Clarion, you can pass in files, keys, queues, screens and now of course windows to
a procedure.  In CDD you cannot get at the fields of a file or the fields of a queue you just have to pass in
the whole queue which means you can OPEN it, you can do GETs, you can do RECORDS() but you can't
actually get at the fields.  If you want to pass in a field you have to do that using one of the typeless
parameters, which are covered in a moment.

If you have passed in a FILE, in CDD, then accessing it will be about 20% slower than if you use a FILE that
is static. ie if you possibly can in CDD, you should access a file that is in your global data area rather than
pass it in as a parameter.  In CW that is completely reversed and a file that is actually passed in as an entity
parameter is faster to access than one that is in your global data segment.  A special issue here is that CW
also now has something called reference data types.  You might like to think of those as pointers but it isn't
always true.  But basically the deal is that in one procedure you can assign a pointer to a file to a global data
item.  Then in any other procedures that you are calling you can access that file using that reference.  That
is generally more efficient than passing parameters all round the place.  You can do the same for keys, and
you can do the same for queues.  

As mentioned earlier, in CDD, with queues you can't get at the fields and you have to pass them in
separately.  In CW we now have user-defined data types.  These are user-defined GROUPs and user-
defined queues.  What this means is that globally you'll define a queue or a GROUP and you'll put the
TYPE attribute on it.  You can then use that queue name in your procedure prototype and when you pass
a queue of that type into your procedure, you can actually access the fields.  That is the most efficient way
of doing things.

You can pass SCREENs and WINDOWs around extremely cheaply.  The down-side is that your USE
variables have to be global or otherwise the procedure you're calling cannot get at them.

Dynamically-typed variables

A traditional truth of all compiled languages is that the compiler has to know, at compile time, what data
types you are passing in to your procedure.  This is not true in CW.  You can say, in your prototype, instead
of a type, either a ? or a *? and these are respectively dynamic and variant type parameters.  What do they
do?  Well, a dynamically typed parameter will take on the type that you pass in but then in your procedure
it will take on any type that you pass to it and it will change its type as you ask it.  Well, what kind of thing
does that give you?  Well that gives you things like genuine dynamic length strings because you can pass
in a dynamic parameter and you can assign anything to it - you can assign some humungous great string
expression and it will change its type to store that string.  So you have genuinely dynamic type parameters. 


The variant types are more interesting still because here you can pass in any data type but when you
actually get to the procedure it will keep that new data type.  So for example, it is these variant *? that you
use if you are passing a queue into a procedure in CW.  Because you can pass in any field and the compiler
will treat that field at the right width and at the right precision.  So for example you can pas in a
DECIMAL(10,2) and it will do DECIMAL arithmetic at two digits of precision.  But you could also pass in a
BFLOAT8 and the compiler would then use BFLOAT8 arithmetic.  You this gives you a way of writing
genuinely polymorphic procedures.  It is extremely easy to write a Browse where you can sort on any field
and that field can be any data type and all of your math and all of your string comparison will be done
correctly.  These are phenomenally powerful.  We actually use these extensively in the production of CW. 
USE variables are done using this technology.  All of the BIND and EVALUATE stuff,  all of the VIEW stuff
is done using this technology.  Of course there is a pay-off, there always is, there's no such thing as a free
lunch and using *? rather than an explicitly typed parameter will make it about 5 times slower.  So it gives
you tremendous flexibility, it gives the ability to re-use code an awful lot, but don't use *? just because you
can't remember what the data type was.  

Windows only...

GROUPs, again.  You can put the TYPE attribute on your GROUP, you then use that name in your
prototype.  Now, to help facilitate this, you can now actually forward reference that, so you can use a name
in your prototype that the compiler doesn't know anything about and providing it's discovered what you
mean by the time you use that procedure, it will work fine.  You can now pass a GROUP of this type  into
a procedure and you can get at each one of those fields by name.

OOP Programmers...

Have you ever done any OOP programming?  Do you feel that you ought to have done?  Many Clarion
programmers do feel this way!

Well, you don't have to be OOP programmers, business applications are not best designed in OOP, but
OOP does have a few good ideas, so I've stolen them and put them in CW.  One of the nice ideas that OOP
has is type extensibility.   A specific example:  I'm writing a generic Browse procedure.  This Browse takes
a queue as a parameter, and this queue has to contain two fields.  It contains a LONG which is an access
into a file and a STRING which is a name.  So the user can now pass that into me correctly and I can use
that queue as he's passed it in.  But, supposing my user, in his calling procedure, has some other things
that he might want to attach to that data.  Things that I didn't know about when I wrote my generic Browse
but things that are now important to him.  What colour he wants it displayed, what time it was put in.  Well
you can add extra fields to your queue and still pass it to the first procedure.  So for a user defined type you
don't have to pass in exactly the same type, you have to pass in a type that is a super-set of the type
underneath.  If you were OOPers I would say 'you have now got genuine polymorphism and encapsulation
and data hiding all built into a standard structural language' with now of the OOP efficiency overhead.  

Additionally to that we now have procedure types.  You can use a TYPE attribute on a procedure, you can
then name that procedure in your procedure prototype and you can pass procedures into other procedures
so that it can call it.  If you don't understand what I just said, that's good, it means that you don't want it,
some people just think that these things are the queen's knees so we've put them in there.  They are
extremely efficient but unless your mind thinks that way, its better to ignore it.  

Routines

What I'm going to tell you today, is the complete opposite of what I told them in Florida  about a year ago. 
The reason for this is that after the Florida DevCon, lots of people came to us and said 'Well, hey, that's
not the way we need ROUTINEs to work, that's not what we use them for' so we changed.  

And that's actually a warning to you.  In all of the stuff that I've told you today, it's the way the system is
currently implemented.  But we're always looking for ways to make things better so if people come and say
'Hey, we use this a lot, we use that a lot', suddenly that will become the way to do it, because that will be
the way we've made it most efficient.

ROUTINEs are now completely individual, separately compiled procedures.  The most significant thing
about that is in fact not at the execution stage, but at the compilation stage.  CDD (and CW) are optimising
compilers.  The difference between an optimising compiler and a non-optimising compiler, is that a non-
optimising compiler compiles your code line by line.  An optimising compiler compiles procedure by
procedure, and its an n-squared process.  That means that if you double the size of your procedure, your
compilation time will be four times longer.  It also means that your chances of busting a compiler limit will
be four times as high.  So now a ROUTINE does not take part in the same procedure as the PROCEDURE
its called from.   So if you're hitting compiler limits, what you do is you take pieces of code and you move
them down into ROUTINEs and they will be compiled separately.  You will also find that they'll be compiled
a lot faster.  For example, if you have (I'm assuming no LOOPs to make things easier) 1000 lines of code
in a PROCEDURE and it takes a minute to compile, if you split it into two ROUTINEs, it should take 17
seconds or so.  So if you're trying to avoid compiler limits and you're trying to make compilation faster, use
ROUTINEs as much as you possible can.  Hands up all those who have hit an isl limit!  (Only one in this
session!)  If you do, you'll know what to do about it.

The DO statement is now extraordinarily cheap, it always costs you exactly three bytes.  For those of you
who know about such things, its a local PROCEDURE call which means a DO in protected mode or CW,
will be roughly 20 times faster than a normal procedure call.

The downside of ROUTINEs now is that access to local variables is dearer inside a ROUTINE than it is
inside a main procedure body.  Access to MODULE data and GLOBAL data is still completely cheap.  so
what that tends to mean is that module data really becomes an issue if you're using ROUTINEs a lot.  You
can use them then effectively as you would in C, in fact.

Bind and Evaluate

A big, big new feature in CW is that we now have run-time expression evaluation.  What that means is that
you can construct strings, or your user can type in an expression, and we will evaluate that expression for
you.  The kind of thing you could do with it is you could produce a general graph-drawing program.  The
user says 'Ok I want to draw a graph of X*7 to power of 92'  and you could just plot that for him because
we would just evaluate that expression.  Of course that is interpreted, that is not compiled so an expression
that is evaluated at run-time will be about seven times slower than one that is hard-compiled.  A special
case of that is that we use this technology in the filter expression of a VIEW.  But because I know how
VIEWs work, the run-time expression evaluation in a VIEW is only twice as a slow as if you hard-code it.

Bindable

The way this works is that you tell the compiler which of your variables you want the user to be able to see
and under what names.  That's the normal way of doing it but as a sort of freebie, we have given you the
ability to BIND a file or BIND a group, and it will go through all of the fields in the FILE or GROUP and it will
make them visible.  To do this you have to put on the BINDABLE attribute and then you can BIND it in the
way I just described.  But, if you use the BINDABLE attribute I have to include all of your field names into
your executable file which pushes up your DOS .EXE size and if you hit memory limitations, it also means
that you would be using more memory  and therefore more resources.  So BIND of a file and BINDABLE
are a nice idea, but only use it if you have to, not because you're lazy!  If you just want a selection of fields
bound, use the templates to generate explicit BIND statements.   You can do that on a GROUP as well  as
a file.

Deep Assignment

In CDD and CW, if you just assign one GROUP to another, that is just a straight STRING assign, it just
copies the whole lot across and that's the fastest way of doing it.  Typically that isn't what you want to do. 
You have some structure that's used as a save buffer, and what you want to save is a few fields from a
large file or something.  A number of other situations like optimistic concurrency is where this quite often
happens.  With CW, you now have something called the Deep Assign statement.

(Tape break...)

As part of this assignment I will do any type conversion that needs to be done, I will go into any sub-groups
and pull out the fields with the relevant names.  The deep assignment structure, one deep assign gives you
an overhead of 50 bytes, but for every field that is assigned there is only an overhead of one byte.  So if
you're assigning across 25 or 30 fields, doing it with a deep assign rather than doing it by expanding in a
template gives you great space and time savings.

You can also deep assign into an array from a single data element, so if you say MyArray :=: 7, that will
assign 7 into every item of your array about 100 times faster than you could do it in a loop.

Questions

In CDD, DECIMALs are slow.  The reason you need to use them is that if you are just using REALs,
numeric computations for finance will come out wrong.  If ever you want to scare yourself, try the following: 
give yourself a REAL variable and assign it 1 followed by 12 noughts.  Then try the following expression: 
MyRealVar + 0.1 - MyRealVar.  Algebra will tell that the answer to that will be 0.1.  In fact you'll find the
answer to that is about 0.0997.  You only have three decimal places of accuracy with what I just did.  Which
means that if you have a billion Rand and you're computing to 1/10 of a cent...   This actually was what
happened, there was a fund, I computed interest for the day, subtracted the two numbers to find the interest
and you have one significant digit.  

So you use the DECIMAL data type because otherwise your answers will be wrong.  But in CDD3, it is slow. 
It is about three times slower than using REAL arithmetic.  If you're doing financial maths and its fairly small
numbers, the trick is to use a LONG and to just divide by 100 before you display it.  That gives you speed
and accuracy.  

In CW that has now all changed.  If you are doing any arithmetic between DECIMALs, PDECIMALs, or @N
pictures, it is now done using a genuine BCD library.  This gives you 31 digits of precision either side of the
decimal point and if you're on a machine without a coprocessor, it will run 5 or 6 times faster than a REAL
will.  If you're on a machine with a coprocessor, it will generally run at about the same speed.  If your
running on a Pentium it will run a bit slower because on a Pentium floating point  operations are effectively
free.

isl Limits

ISL limits is basically our optimiser saying its run out of data space.  It happens as your procedures grow
bigger.  If you imagine, an optimising compiler works by building up a network of how your procedure works,
and then thinking about it.  As you procedure gets bigger, this network gets bigger & bigger & bigger.  So
the way to reduce this problem is just to move parts of your procedure out so your network becomes
smaller again.  