[comp.lang.c] Efficient coding considered harmful?

bill@twwells.uucp (T. William Wells) (10/27/88)

Sorry I wasn't able to participate much in the discussion on efficient
coding, but between setting up my new computer, a wedding
anniversary, computer problems at work, thinking about comp.archives,
and lots of other things, I have had little or no time to write, and
certainly not about anything complex. Anyway, here goes...

It's the damnedest thing, but people whose opinions I otherwise
respect seem to have this thing about coding efficiently.  They don't
like it. Worse, they discourage it.

Well, phooey.

---

I advocate training oneself to be one who codes efficient programs. I
do not mean writing code and then making it more efficient, I mean
writing it that way in the first place. One should make it a practice
to consider which coding methods are best on each architecture, so
that it is second nature to code efficiently. Efficient coding should
take its place right along with easily understood coding as something
every coder should do, as a routine part of the job.

I have heard three main counterarguments to this:

1) "The compiler ought to handle that, so you shouldn't bother with
   it." What nonsense! We have to code in the real world, not the
   world of make-believe and ought-to.

2) "What is more efficient on one machine may well be less efficient
   on another." Well, this is a good argument for knowing a lot of
   different machines and adapting one's coding style to the relevant
   machines, but this is certainly not a good argument for avoiding
   efficient coding. What it boils down to is an argument that a
   program which is coded in ignorance of efficiency may well be
   better (efficiency-wise) than one which is coded to be efficient,
   albeit only for some set of architectures. Well, could be, but I
   wouldn't bet on it.

3) "Efficient coding makes obscurer programs." Well, since most
   efficient coding involves minor changes from the inefficient way
   of doing things, changes which are mostly a matter of style rather
   than of organization, this argument should really be read: "*I*
   don't like to or find it hard to read that kind of program, so it
   is unclear." Well, speak for yourself!

---

[I wrote about improving other people's code. I mentioned that
following certain coding practices, eliminating "fat", and doing so
throughout a program, I was often able to improve the execution time
by 25% or more.]

From: rwberry@hubcap.UUCP (Robert W Berry)
Message-ID: <3105@hubcap.UUCP>

:     I've been programming for YEARS, and if it's not a professional trade
: secret, I'd love it if you would summarize your "coding practices" and
: email them or post them.

They are no real secret but I haven't really thought hard about
organizing them for presentation, so I can't just tell you about
them. However, some of the suggestions that I have seen posted have
been similar to what I do. The next time I go over someone else's
code, I'll see if I can remember to make notes.  Following are some
suggestions right off the top of my head, take them with the
requisite dollop of thinking for oneself.

The first thing: understand the kinds of architectures where
efficiency matters.  If you are coding a game, you can be reasonably
sure that efficiency is only of concern on relatively conventional
architectures, so make yous assumptions accordingly.  If you are
coding some kinds of numerical stuff, where the greatest efficiency
ought to be obtained on an array processor, keep in mind the
characteristics of array processors when optimizing code.  The more
you know about the processors that your code could have to run on,
and the better your analysis of which ones efficiency matters on, the
better will be the decisions you make on which coding practices to
adopt.

Read compiler design books. Whatever they tell you is good for the
compiler is also good for you the coder.  They talk about common
subexpression elimination: when you write code, look for common
subexpressions and eliminate them. (But watch out: not all common
subexpressions should be eliminated, this too is a matter of
judgegment and experience.) They talk about strength reduction, you
look for places where strength reduction is possible.  Note that
*you* can do a better job of this than the compiler can. *You* are
capable of inductions that the compiler is not. *You* can find
optimizations that are not possible to discover mechanically. This is
one reason why the argument "leave it to the compiler" is often wrong.

Along this line, look for redundant calculations: sometimes it is
cost effective to store the result of a previous calculation instead
of recomputing it. In particular avoid recalculation in the control
part of iterative statements. Many's the time that I have got
significant improvements just by turning:

	for (i = 0; i < a[j]; ++i) ...
into
	for (i = 0, ilast = a[j]; i < ilast; ++i) ...

or some equivalent.

Use register variables. And take a litle pain to get them right.  A
technique that works enough of the time is this: break up a routine
into groups that are executed with about the same frequency.
Guessing is OK. Starting with the most frequent, count the number of
references to each automatic. If the numbers are sufficiently
different, order them by those numbers. Where they are not, use the
next most frequent group to decide the order. Repeat till you run out
of groups or variables. This is only a heuristic, but it can be done
on the fly, or with simple programming tools, and is certainly better
than not specifying register variables at all.

Avoid excess function calls. A lot of people have been brainwashed by
some modularity gurus into believing that every identifiable "whole"
should have its own function. What I have found is that this makes
the program LESS maintainable than otherwise.  And, of course,
programs written this way do a lot of function calls. My own recipe
divides functions into two classes: those that control the operation
of other functions and those that do the dirty work. The control
functions should generally restrict themselves to if's and function
calls and maybe some initialization; the other functions can do
whatever is needed. If the grunge functions start to get too
complicated, start from the outside when splitting up the function:
this keeps down the number of function calls while getting the most
decomposition for your effort.

Don't use goto. Yes, oddly enough, this can make programs slower.
Many optimizers that I know of give up on any function that contains
a goto. So adding a goto may very well slow down your code.

---

From: g-rh@XAIT.XEROX.COM (Richard Harter)
Message-ID: <34112@XAIT.XEROX.COM>

:         I would say that we all know what Bill is talking about
: here, except that "we" all obviously don't.  Basically this is
: using hand optimization as a default in coding style.  One can take
: the view that a good optimizer will do many of these things, e.g.
: use of temporaries, moving static expressions outside loops, placing
: the appropriate variables in registers, etc.  One can also take the
: view that the capabilities and realities of optimizing compilers are
: less than claimed.  Hand optimizing is safer across a variety of
: environments.
:
:         In my experience hand optimizing in original code is less
: efficient, in the sense of code written per unit time, then writing
: code initially in a "clear" style first, and then hand optimizing
: afterwards.  In short, write it in the simplest way first, get it
: working, and then improve performance.

This is not quite what I am advocating. Rather than hand-optimizing
as you code (though one should certainly do a certain amount of
this as a matter of course), I am advocating developing a mindset
where this kind of "optimizing" becomes your natural coding style.
This does mean developing more than one "natural" coding style, but
that is no more a problem than our habit of using different speech
patterns in different social situations.

As an example of what I mean, when I am coding a program that I
expect to be run on one of the "normal" machines, I don't write my
code as

	for (i = 0; i < ALAST; ++i) {
		... A[i];
	}

and then change it to:

	for (ap = A; ap < &A[ALAST]; ++ap) {
		... *ap;
	}

I write it this second way, as a matter of course, because I thought
about it for a while and decided that this was the appropriate way to
do it.  I then trained myself into the habit of doing it this way.
Now, I don't spend *any* extra time getting the advantages of the
latter formulation. As a necessary and important side effect I also
became comfortable with such things and now treat them as no more
abnormal or incomprehensible than *ptr++.

---

From: bright@Data-IO.COM (Walter Bright)
Message-ID: <1700@dataio.Data-IO.COM>

: Here's some of the things I do:
:     o   Organize complicated if statements so the result of the calculation
:         is known by evaluating it as little as possible. For instance,
:                 if (a && b && c)
:                         ...
:         organize such that if a is easy to evaluate and usually FALSE, put
:         it first.

This also generalizes to sequences of if-else statements.

:     o   Replace stuff like,
:                 if (a)
:                         x = b;
:                 else
:                         x = c;
:         with,
:                 x = a ? b : c;
:         Many compilers generate better code for the latter.

I have to disagree with this.  About the only time that this helps is
when combining the statements gives greater scope to the optimizer.
This happens because many optimizers won't optimize across
statements. Unfortunately, this particular change, when done with
complicated expressions, can make the code much harder to read, so it
must be done with care.

:     o   Use the ^ operator because many times,
:                 a = !a;
:         should really be,
:                 a ^= 1;

However, some machines can't do ^ very well, so take care.

:     o   Avoid things like,
:                 a %= 4;
:         Replace with,
:                 a &= 3;

Generally true, but watch out for negative numbers. Also, dividing
positive numbers by powers of two should be done with a shift.

:     o   Organize control flow such that the most common cases go straight
:         through, this is most important for pipelined machines.

While this is true, this tends to make the code *much* less readable,
even when you can get the optimizer to cooperate. So I don't do this
one.

:     o   I gag when I see things like,
:                 a = strlen("asdf") + 1;
:         instead of,
:                 a = sizeof("asdf");
:         The strlen case above was even printed in a magazine recently to
:         'prove' that assembler was better than C!

Gag. This is a specific example of a general case: never do at run
time that which you can get the compiler to do at compile time.
However, beware of getting the compiler to do floating point for you:
some won't, and others will do it differently in the compiler than
they would have at run time.

:     o   Try to improve the 'locality' of operations, i.e. move calculations
:         as close as possible to the point where they are used. This helps
:         most compilers to utilize registers better.

Good point. I hadn't thought of doing this. And it should often make
the reader's job easier, bringing related things closer.

:     o   Replace int variables with unsigned where possible. This tells the
:         optimizer that the variable can never be negative, making certain
:         optimizations possible.

Unfortunately, this one is likely to make the optimizer not do certain
optimizations that the compiler writer didn't think were useful for
unsigned. Sigh. Not only that, but I have discovered more bugs dealing
with unsigned things...

:     o   Put the most frequently accessed member of a struct first, so the
:         offset is 0.

Yes. And put scalar arguments before aggregate arguments in your
functions.

:     o   Use char arrays instead of int arrays where possible.

Accessing characters on some systems causes a fair amount of code to
be generated. For example, on a 68000, accessing an unsigned character
to be used as an integer adds an instruction to each read.  Accessing
a signed character might involve two extra instructions.

:     o                                                         Adjust other
:         arrays so that the size of the array elements are a power of 2.
:         (Making the address calculation a simple shift.)

Be careful if you are making the arrays larger, you may make the
program slower. Demand paging systems and swapping systems both can
do you dirty when the program data is bigger.

:     o   Avoid bit fields, most especially signed ones.

Try: don't use them at all, they tend to be nonportable. Of course,
if you have a good compiler on a VAX...

:     o   Use realloc instead of malloc/free pairs. Use calloc instead of
:         malloc followed by zeroing each member.

Efficient memory allocation is *much* more difficult than this, what
you really need to do is to cut down on the number of calls to malloc
and free. Of course, that usually means writing some kind of
allocation stuff yourself, never an easy task.

Realloc has to copy data, so this should be less efficient than just
doing another malloc & free.

Also, calloc is nearly useless for clearing structures that contain
pointers.  It is just not portable to assume that a bit pattern of all
zeros is equivalent to a null pointer.

---

From: gwyn@smoke.ARPA (Doug Gwyn )
Message-ID: <8630@smoke.ARPA>

: >    o  Avoid things like,
: >               a %= 4;
: >       Replace with,
: >               a &= 3;
:
: You're assuming a is nonnegative.  Any decent compiler will
: perform such instruction replacements for you.  Write whatever
: is clearest in context.  To get a remainder, % is clearer.

You are assuming that the compiler can figure out that a is
nonnegative. Unless declared so, it is unlikely that a compiler will
figure this out. However, the human can. And, again, clarity is often
merely in the eye of the beholder. *I* don't have any problem
interpreting the & as a remainder operation.

: In the case that the "4" might change, parameterizing the
: first case will give correct code after it changes, while
: the second will break unless another power of two is used
: for the replacement for "4".  Thus the first is SAFER.

Unless, of course, the programmer remembered to COMMENT.

: >    o  Combine printfs,
: >               printf("aksdf aksjdhf kahdf jhdsfhj\n");
: >               printf(" asdkljfhkajshdf djfh kjahsdfkja h\n");
: >       Convert to,
: >               printf("aksdf aksjdhf kahdf jhdsfhj\n\
: >                asdkljfhkajshdf djfh kjahsdfkja h\n");
:
: Thereby introducing a bug (exercise for the student).

The spaces. Two black spots for K&R string continuation.

: The difference in time between these is negligible,
: but if you're really tweaking for efficiency puts()
: might have been better, depending on the implementation.

But I'd think of this as a *space* optimization, not a speed
optimization. And I know of several programs where just this one
optimization could save 5-10% in the program size.

: ...
: A related point is to declare local variable in blocks
: rather than all at the beginning of the function body.

I've found that this can actually make the code harder to read, by
making it difficult to find the declaration of a variable. My own
practice is to declare things in just four places: in an include file,
at the top of a source file, at the top of a function, and within a
block, but only if the whole of the block will fit in a 20 line
window.

: >    o  Put the most frequently accessed member of a struct first, so the
: >       offset is 0.
:
: Not all architectures can access the 0 offset faster than
: the others.  I knew of one that was actually slower.

But, unless one really cares about this oddball situation, one should
do it anyway.

: >    o  Avoid struct function parameters and return values.
:
: This is a matter of interface design.  Small structs such
: as one might use to represent a complex number are not a
: problem; large structs are more quickly (though less conveniently)
: passed by reference, so long as not too many references to
: the members are made inside the function.  Forcing the
: caller to allocate the structure may not be convenient.

I avoid structure passing and returning. There are still enough
compilers out there that don't handle them, or that handle them wrong
that portability considerations keep me from using them.

---

From: bright@Data-IO.COM (Walter Bright)
Message-ID: <1704@dataio.Data-IO.COM>

: Micro-efficiency gets regularly denounced. Here are some counterarguments:
: o       If your commercial product is slower than the competition, you
:         won't get a chance to take advantage of the maintainability/
:         portability advantages because you'll be out of business.

Or if it is larger. This discussion has focused on making programs
faster, but making them smaller is also relevant. I do know that we
have lost customers because someone else offered a smaller program to
do the same job.

: o       The sum of all the tweaks can make the difference (on the PC)
:         between using a small memory model and the large. This gives
:         a lot of leverage to seemingly minor changes.

The sum of all tweaks can make a difference on lots of machines.
Consider the Mac 32K segments, the PDP-11 64K+64K address space, the
user forced to wait for 10 seconds instead of 4 because of the 5%
slower routine which caused the scheduler to lower his priority....

: o       I derive a lot of personal satisfaction from creating 'lean
:         and mean' code.

This is often disregarded: good programmers get that way because they
*care*, and if they care, they will insist on doing the best they
can. And that should include these "tweaks".

---

Phew... I finally got it all answered.  On to the next subject...

---
Bill
{uunet|novavax}!proxftl!twwells!bill

gwyn@smoke.BRL.MIL (Doug Gwyn ) (10/28/88)

In article <119@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
>It's the damnedest thing, but people whose opinions I otherwise
>respect seem to have this thing about coding efficiently.  They don't
>like it. Worse, they discourage it.

I think it would be more accurate to say that we want to discourage
excessive concern with microefficiency to the detriment of other
important attributes of source code.

>2) "What is more efficient on one machine may well be less efficient
>   on another." Well, this is a good argument for knowing a lot of
>   different machines and adapting one's coding style to the relevant
>   machines, ..

Actually the force of this argument is stronger if you already
appreciate the value of coding for reasonable behavior on almost
any C implementation, rather than "optimum" behavior on just one.

Some people really may never port their code outside the first
system it's compiled for, but many of us do and we simply don't
have time to fine-tune the code in each environment (unless we
will personally accrue enough benefit to justify it).  My own
time is worth much more than any computer's.

>3) "Efficient coding makes obscurer programs."

Certainly going overboard with microefficiency tweaks does.

>	for (i = 0; i < a[j]; ++i) ...
>into
>	for (i = 0, ilast = a[j]; i < ilast; ++i) ...

In most cases, the following can be used:
	for (i = a[j]; --i >= 0; )
which is more efficient and clearer.

(But don't write something like
	for (p = &a[j]; --p >= a; )
which is nonportable.)

>	for (i = 0; i < ALAST; ++i) {
>		... A[i];
>	}
>
>	for (ap = A; ap < &A[ALAST]; ++ap) {
>		... *ap;
>	}
>
>I write it this second way, as a matter of course, because I thought
>about it for a while and decided that this was the appropriate way to
>do it.  I then trained myself into the habit of doing it this way.

This makes a good example of the dangers of overconcern with such
matters.  There are quite a few compilers that, in many cases, will
generate much better code for the first method than for the second,
because they can recognize an opportunity to use vector instructions.
Others will generate essentially the same code for the two methods.
Unless the situation is naturally thought of as involving a pointer
(for example, to a structure member of an array), it may be clearer
to treat array elements as array elements.

>... dividing positive numbers by powers of two should be done with a shift.

No!  Juggling the source code to use bit-oriented operations in an
arithmetic context not only makes the code less portable and harder
to maintain, it can also lead to future errors, when for example a
chunk size is changed to a variable rather than a constant, or to a
non-power-of-two.

>Unless, of course, the programmer remembered to COMMENT.

Comments don't necessarily track the code, and it is unrealistic to
expect every use of the kind of tricks you advocate to be commented.
Besides, the failure may very well result when code is changed
somewhere far from the place of the comment.

I agree that any use of a trick ought to be clearly commented, but
then whenever I find myself doing that I also usually figure out a
good non-tricky way to accomplish the goal and use that instead.

djones@megatest.UUCP (Dave Jones) (10/28/88)

From article <119@twwells.uucp>, by bill@twwells.uucp (T. William Wells):
...
> 
> Phew... I finally got it all answered.  On to the next subject...
> 

Dream on.

scs@athena.mit.edu (Steve Summit) (10/28/88)

In article <119@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
>It's the damnedest thing, but people whose opinions I otherwise
>respect seem to have this thing about coding efficiently.  They don't
>like it. Worse, they discourage it.

Some time ago I came to the conclusion that the style vs.
efficiency debate is another religious war, which I usually try
to stay out of.  Good style and, I'll admit it, anti-efficiency
is a personal crusade, though, so I can't help but respond.

None of this should be taken as a personal attack against Mr.
Wells, or anyone else, for that matter.  His points are well
taken; I simply have a different point of view, going back to
first principles, as all good religious wars do.  When caught
between a rock and a hard place, when something's got to give,
some people shrug their shoulders and sacrifice style,
modularity, or portability.  I am prepared to sacrifice
efficiency.  (Most of the time I can have my cake and eat it too,
and I have any number of general techniques for doing so, which I
will probably defer to a separate posting so that those who are
sensibly ignoring these diatribes can still see them.)

>I advocate training oneself to be one who codes efficient programs. I
>do not mean writing code and then making it more efficient, I mean
>writing it that way in the first place.

Here I agree with at least some of the words, but not the
implications.  It happens that I usually write nice, efficient
programs, but not by resorting to microefficiency tweaks or by
sacrificing readability in any way.  It is important to employ
lots of common sense (and, of course, "common sense isn't"); to
shy away from grotesquely inefficient algorithms; and to partition
the code so that key sections can be cleanly replaced later if
efficiency does prove to be an issue.  It isn't important to
write every last expression and statement in the most
theoretically blistering way possible.

>I have heard three main counterarguments to this:
>1) "The compiler ought to handle that, so you shouldn't bother with
>   it." What nonsense! We have to code in the real world, not the
>   world of make-believe and ought-to.

Many of the more egregious examples of source-level
microefficiency tweaking are, in fact, routinely handled by
today's (and even yesterday's) compilers.  Consider replacing
power-of-two multiplies by left shifts, a textbook example.
I just booted up my pdp11 (I am not making this up), and its
optimizer knows how to shift left when I multiply by two.  (To be
sure, not all newer compilers have been better.)

>3) "Efficient coding makes obscurer programs." Well, since most
>   efficient coding involves minor changes from the inefficient way
>   of doing things, changes which are mostly a matter of style rather
>   than of organization...

I don't know what is meant by "most efficient coding."  The
interpretation "the most efficient coding involves the most minor
changes" is probably not the one that was intended, although I
like it, because taken to its extreme it says not to make any
changes at all.  I cannot agree with the more likely
interpretation.  The minor changes, the replacements of
multiplies and divides by shifts and the like, are mostly noise
(both with respect to efficiency and style).  The really big
efficiency hacks, the ones people spend weeks and months on, do
involve massive organizational changes and wholesale concessions
to readability and maintainability.

>   ...this argument should really be read: "*I*
>   don't like to or find it hard to read that kind of program, so it
>   is unclear."

The attitude of most C experts is "*I* don't have any problem
reading this code; anyone else who considers himself a Real C
Programmer shouldn't, either."  This attitude is patronizing and
wrong.  To borrow a previous argument, "We have to code in the
real world, not the world of make-believe."  There are plenty of
people out there who are still learning C, and since C has become
as popular as it has and C programmers are in demand, many of
them are working for real companies writing (and maintaining)
real programs.  A few painless concessions (like multiplying
instead of shifting, or making comparisons against NULL pointers
explicit) really do substantially increase the readability of
code by mere mortals.  (I maintain that every unnecessary
translation from an encoding back to an intended meaning, no
matter how intuitive to the experienced programmer, incrementally
increases comprehension time, effort required, and probability of
error.)

>...look for redundant calculations: sometimes it is
>cost effective to store the result of a previous calculation instead
>of recomputing it.

...but be scrupulously careful, and comment the stored result
well, because doing so is one, extremely common, "concession to
maintainability."  One of the all-time, number one bugs is the
redundant variable whose value becomes wrong because one of the
precedent variables changes, but the calculation is not repeated.
If you use n squared at several different places within a longish
section of nontrivial control flow, and you try to replace it
with a new variable nsq which is set to n*n just once, sooner or
later someone (probably you) is going to make some change to the
program (either a new assignment to n, or a new wrinkle in the
control flow) which results in nsq not containing the square of
the current n.  If you leave the explicit n*n everywhere, this
error is impossible.

This advice, like almost anything, must be applied on a
case-by-case basis.  When the control flow is straight-through
and all occurrences of the common subexpression are well-
localized, a temporary variable is often a good idea, not so much
because it's easier to type or it might be more efficient, but
because it makes it easier for the reader to prove that the
identical quantity was really used in both (or all three, etc.)
places.

>Use register variables. And take a litle pain to get them right.  A
>technique that works enough of the time is this:...

I'm sorry, I don't have spare time or spare brain cells for
following or utilizing a technique like this.

When I was a beginning C programmer, I decided not to use
register variables at all in the first pass of coding, because I
figured that, when I eventually got the program working, somebody
was going to say that it wasn't fast enough, and if I had already
blown all of my ammunition, how was I going to speed it up?
Lately I throw in "register" all the time, but in some ways I
respect my original attitude more.  The point is, if it's the
sort of program that people notice how fast it is, they would
have complained even if I had used register variables since day
one.

I have also always felt that this is one area where the compiler
really should be doing the work.  Many modern compilers are very,
very good at register allocation.  It is true that the
historically popular compilers (specifically, the Unix ones) are
not, so it is good practice to put "important stuff" in
registers, but don't spend a lot of time on it unless you have
to.  (What's "important stuff?"  A ridiculously simplistic rule
of thumb for using "register" in C, albeit one of the two I use,
is "on any variable named 'i' or 'p'.")

>Avoid excess function calls. A lot of people have been brainwashed by
>some modularity gurus into believing that every identifiable "whole"
>should have its own function. What I have found is that this makes
>the program LESS maintainable than otherwise.

Here's another religious debate: the structured programming one.
Nevertheless, good modularity is the only way to keep a large
program maintainable.  Of particular pertinence to the theme of
this article is that efficiency-critical modules, if properly
isolated, can be implemented quickly and (possibly) inefficiently
first, while getting the program to work correctly at all, and
then transparently replaced later with a more efficient version,
if and only if it is found that the quick, inefficient
implementation is really too inefficient after all.

If people have a bad impression of highly modular code, it is
because they (or, more likely, their predecessors) have used what
I call a "Ginsu knife" approach to modular decomposition, whereas
the correct instrument to use is a scalpel.  If you went to a
lecture once on modularity but the only guideline you remember is
"50 source lines," you're going to use split -50 on your
monolithic source files, and not garner any of the benefits of
good modularity.  If, on the other hand, you learn how to
modularize well, you just won't believe how easy your life (with
respect to software development and maintenance, anyway) will
become.

There are a few applications (and, some believe, a few
architectures) for which function call overhead is significant,
but they are infrequent, and in general there should be
absolutely no stigma attached to a function call.  It's usually
easy to back a function call out later, if you have to.  (The
normal way is with macros, but be careful lest the semantic
differences between macros and functions hamper code quality.)

>Rather than hand-optimizing
>as you code (though one should certainly do a certain amount of
>this as a matter of course), I am advocating developing a mindset
>where this kind of "optimizing" becomes your natural coding style.
>As an example of what I mean, when I am coding a program...
>I don't write my
>
>	for (i = 0; i < ALAST; ++i)
>		... A[i];
>
>and then change it to:
>
>	for (ap = A; ap < &A[ALAST]; ++ap)
>		... *ap;
>
>I write it this second way, as a matter of course.

I am in complete agreement that there is a large class of good
coding practices which is far better learned well and adopted
from the beginning than applied retroactively.  (I disagree that
source-level microefficiency belongs in this class.)  The
judicious use of pointers does belong in this class, once you're
comfortable with them, and as long as they're appropriate for the
problem at hand.  If you're not coding for a particular machine,
or if it's low-frequency code, don't worry about using array
notation if it makes more sense.  Among other things, it may not
make a speed difference, after all.

Just yesterday a colleague and I were working with an FFT
routine.  (Interestingly enough, it was in the book "Numerical
Recipes in C".)  The first difficulty was the use of shifts
instead of multiplies and divides.  My colleague is a fine
mathematician, and a competent C programmer, but "m=n>>1" just
wasn't leaping off the page and saying "divide by two" for him,
and he was interested in seeing how the algorithm worked.  I was
particularly annoyed with this particular shift, because it was
in initialization code, so any savings wouldn't matter.  Then I
realized that there were also some shifts in inner loops, and was
about to concede that the shifts in the initialization section
were justifiable, for consistency's sake.  Then I noticed that
the "m=n>>1", which did appear in a loop, operated on a constant
n which had been computed with "n=nn<<1", so if efficiency really
mattered, a simple "m=nn" could have been used.  (Side note on
style: a variable called "nn" usually means twice "n," not the
other way around.)  Then I noticed an "istep=2*mmax" (also in a
loop), thus demolishing any consistency argument.

Shift vs. multiply is actually a side issue here.  Things got
interesting when I noticed that, in spite of their apparent
concern with efficiency, the authors of the FFT code had used
nothing but array references, even in the inner loops of the code
(doubtlessly because it had been translated from Fortran).
"Well," I said to myself, remembering a favorite sentence from
The Elements of Programming Style, "FFT's are an area in which
efficiency does matter in practice, so let's see what happens if
I replace those array references with pointers."  The result, to
my considerable surprise, but reinforcing the point all the more,
was that there was no difference (30.59 vs. 30.54 seconds for 20
512-point FFT's on a PC AT.)

I know that there are machines on which a difference would have
been evident.  The point here is that there are also machines for
which there is no difference at all, either because they can
implement subscripts efficiently, or because the floating-point
operations completely dominate the timing.  Previous posters have
noted that there are also machines for which pointers could
actually be slower.

>:     o   Replace [simple if/then with ?:]
>I have to disagree with this.
>statements. Unfortunately, this particular change, when done with
>complicated expressions, can make the code much harder to read...

Agreed.

>:     o   Organize control flow such that the most common cases go straight
>:         through, this is most important for pipelined machines.
>While this is true, this tends to make the code *much* less readable...

Absolutely.  Control flow should be determined almost entirely by
readability and stylistic considerations.

>:     o   Avoid bit fields, most especially signed ones.
>Try: don't use them at all, they tend to be nonportable.

This is a side question: what's so unportable about bitfields?
Sure, the layout (left to right or right to left) isn't defined,
but that's only a problem if you're trying to conform to external
layouts, which is a "problem" with structures in general.  (The
solution is neither "don't use bitfields" nor "don't use
structures" but "don't try to portably conform to external
layouts.")  The ordering could also be a problem if the code
internally depended on it in some way, but this is no more or
less a problem than programs which depend on byte ordering
within words.

Are there substantial numbers of real (not toy) C compilers that
either don't implement bitfields, or that don't implement them
correctly?  ("Correctly" as defined by K&R and ANSI, not by some
program that is trying to use them nonportably.)

>:     o   Use realloc instead of malloc/free pairs. Use calloc instead of
>:         malloc followed by zeroing each member.
>Efficient memory allocation is *much* more difficult than this, what
>you really need to do is to cut down on the number of calls to malloc
>and free. Of course, that usually means writing some kind of
>allocation stuff yourself, never an easy task.

Please don't avoid malloc; the alternative is generally
fixed-size arrays and "lines longer than 512 characters are
silently truncated."  Please don't roll your own allocation
routines, again unless you have to; this is the kind of low-level
dirty work, hard enough to do well, that you should let the lower
levels (i.e. the standard implementations of malloc) give it
their best shot.

>Also, calloc is nearly useless for clearing structures that contain
>pointers.  It is just not portable to assume that a bit pattern of all
>zeros is equivalent to a null pointer.

Absolutely correct.  Why does calloc exist?  Of what use is it?
Why has ANSI legitimized it?  In principle, it is equally useless
for clearing structures that contain floating-point fileds.

>: >    o  Avoid things like,
>: >               a %= 4;
>: >       Replace with,
>: >               a &= 3;
>
>: In the case that the "4" might change, parameterizing the
>: first case will give correct code after it changes, while
>: the second will break unless another power of two is used
>: for the replacement for "4".  Thus the first is SAFER.
>
>Unless, of course, the programmer remembered to COMMENT.

If the code reads

	a &= 3;		/* really a %= 4 */
or
	a &= 3;		/* really a %= HASHSIZE */

and I do a global replace of 4, or re#define HASHSIZE, the
comment may not help.

>: Micro-efficiency gets regularly denounced. Here are some counterarguments:
>: o       If your commercial product is slower than the competition, you
>:         won't get a chance to take advantage of the maintainability/
>:         portability advantages because you'll be out of business.
>Or if it is larger. This discussion has focused on making programs
>faster, but making them smaller is also relevant.

I agree, and find code size far more frequently relevant in
practice (among other things, because of the aforementioned
pdp11).  Remember that the classic tradeoff is between time and
space, so the fancy efficiency hacks often make the code larger.
On the other hand, there is a quote of Brian Kernighan's or
Dennis Ritchie's (which I can't find at the moment) which points
out that, counter to the tradeoff, clean, tight code is often
smaller and faster.  (Emphasis on clean.  Of course, I advocate
sneaky hacks to decrease code size as much as I advocate
efficiency hacks, i.e. not at all.)

I might also point out that the marketplace generally rewards the
product that comes out first, so if you can compress your
development schedule by using clean, straightforward design and
eschewing time-consuming and bug-prone efficiency tweaking,
you may come out ahead (and sew up your market share and your
pocketbook by releasing a speeded up version 2 six months later).

>: o       The sum of all the tweaks can make the difference (on the PC)
>:         between using a small memory model and the large. This gives
>:         a lot of leverage to seemingly minor changes.
>The sum of all tweaks can make a difference on lots of machines.
>Consider the Mac 32K segments, the PDP-11 64K+64K address space, the
>user forced to wait for 10 seconds instead of 4 because of the 5%
>slower routine which caused the scheduler to lower his priority....

While these considerations are real, they should be viewed as
unfortunate and temporary limitations which will be resolved,
hopefully soon enough, at the root level, and not something which
we resign ourselves to working around forever, and which we get
so used to working around that the workarounds become part of the
language, persisting well beyond their need.  These are what Fred
Brooks calls "accidental problems," and every programmer minute
and line of code spend catering to them is not being spent on
real problems.

>: o       I derive a lot of personal satisfaction from creating 'lean
>:         and mean' code.
>This is often disregarded: good programmers get that way because they
>*care*, and if they care, they will insist on doing the best they
>can. And that should include these "tweaks".

I'll third the motion, but without the tweaks.  My proudest code
is that which is not only small and fast and elegant and portable
and modular and extensible but also patently obvious in function
to the casual observer, and demonstrably correct without
exhaustive analysis.  That may sound like a tall order, but I
think I'm fairly successful at it, and as I've implied I'll
compromise on the first two (size and speed) before the rest, and
the last two (obvious and demonstrably correct) are non-negotiable.

			*	*	*

I'm not going to try to second-guess all of the replies I will
undoubtedly get from the efficiency aficionadoes out there, but
I will mention two things.

I keep saying "don't do <something> unless you have to," by which
I mean after actual experiments on prototype code demonstrate
that there is a problem.  The attitude of "don't worry about
efficiency until later" is frequently denigrated as leading to
unsalvageably inefficient code, because trying to patch in some
efficiency later is tantamount to a rewrite.  Although this can
be true, the solution is to teach people good, responsible
programming practices early, avoiding gratuitous and unnecessary
inefficiency, without teaching them to "optimize too early."

It's been said before, and I'll say it again and keep saying it:
Don't Optimize Too Early.  If, once the program is working
correctly, it's too slow, profile it and then see about
optimizing the routines that will make a difference.  This
absolutely does not mean that you should write throwaway code; if
the prototype is a hodgepodge, you won't be able to go back in
and speed it up cleanly, and you also won't be able to go back
and add any features without breaking it (on the off chance that
it isn't badly broken already).

Responsible programming practices are just what the articles I am
reacting to are trying to formulate, and all I'm trying to do is
to draw the line a little more finely between what's reasonable
and what's overboard.  My second point, and the reason I'm taking
all of this time and space replying, is that people who are
learning programming (and we're all still learning programming)
are much more impressionable than you might think.  There's
nothing wrong with this; it's actually refreshing given that it
is often supposed that people stop learning at around age 20.
But it does mean that if you unilaterally tell a reasonably green
programmer to use pointers instead of arrays, he'll spend months
having lots of frustrating problems with pointers, and likely end
up hating them and the language in general.

Even the most obfuscated efficiency tweaks do occasionally have a
place, when speed really matters and the hardware (or whatever)
isn't up to it, but these techniques tend to be discussed and
advocated out of proportion to their usefulness.  None of us need
more excuses or encouragement to write tricky code.  There are
far better problems to be expending effort on.

                                            Steve Summit
                                            scs@adam.pika.mit.edu

guy@auspex.UUCP (Guy Harris) (10/28/88)

>:     o   Use the ^ operator because many times,
>:                 a = !a;
>:         should really be,
>:                 a ^= 1;
>
>However, some machines can't do ^ very well, so take care.

And some code for reasons of, well, *microefficiency* may rely on the
fact that any non-zero value, not just 1, is considered "true" by C; the
only safe way of inverting such a boolean is with "a = !a". 

Too bad C doesn't have a Boolean type....

guy@auspex.UUCP (Guy Harris) (10/28/88)

>:     o   Use realloc instead of malloc/free pairs. Use calloc instead of
>:         malloc followed by zeroing each member.
>
>Realloc has to copy data, so this should be less efficient than just
>doing another malloc & free.

Err, umm, "realloc" is often used when you have e.g. an array with N
filled-in elements, and you want to add some more elements; in this
case, if you do another "malloc" and "free", you'll have to copy the
entire array *anyway*; some "realloc" implementations may be able to
avoid this if they can tell that the extra data can simply be pasted
onto the end of the existing array.

knudsen@ihlpl.ATT.COM (Knudsen) (10/29/88)

In article <8775@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn ) writes:
> (But don't write something like
> 	for (p = &a[j]; --p >= a; )
> which is nonportable.)

Why not portable?  I know there are some weird architectures that
may treat pointers strangely.  But assuming that p is declared apointer
to the same type as a[], don't the official semantics of C
guarantee that the above code is valid?
What am I missing?
-- 
Mike Knudsen  Bell Labs(AT&T)   att!ihlpl!knudsen
"Lawyers are like nuclear bombs and PClones.  Nobody likes them,
but the other guy's got one, so I better get one too."

henry@utzoo.uucp (Henry Spencer) (11/01/88)

In article <7421@ihlpl.ATT.COM> knudsen@ihlpl.ATT.COM (Knudsen) writes:
>> 	for (p = &a[j]; --p >= a; )
>> which is nonportable.)
>
>Why not portable? ...

It terminates by running p off the beginning of a, and comparing it to
a to detect this situation.  The result of running a pointer off the
beginning of an array is (and always has been) undefined.  On some
segmented machines, this can and does cause trouble.
-- 
The dream *IS* alive...         |    Henry Spencer at U of Toronto Zoology
but not at NASA.                |uunet!attcan!utzoo!henry henry@zoo.toronto.edu

bright@Data-IO.COM (Walter Bright) (11/01/88)

In article <7421@ihlpl.ATT.COM> knudsen@ihlpl.ATT.COM (Knudsen) writes:
>In article <8775@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn ) writes:
>> (But don't write something like
>> 	for (p = &a[j]; --p >= a; )
>> which is nonportable.)
>Why not portable?

If a was created by a call to malloc, and (sizeof(a[0])>2), then on the 8086
in large memory model, p can NEVER be less than a! The trouble results from
malloc creating a segment for a. Decrementing p past a causes a segment
wrap, exactly like decrementing an unsigned variable past 0.

This behavior is allowed for by ANSI C, and is the most reasonable way
of doing things on the 8086.

pjs269@tijc02.UUCP (Paul Schmidt ) (11/01/88)

    After working for a year to optimize a DBMS I
have some comments on writing efficient code.

    "More harm has been done in the sake of
    efficiency than any thing else."

    When I was optimizing I found some horrendous
"efficient coding" practices that were used that
made the code either less managable, less efficient
or both.

    Take, for example, my favorite:

some_function(a_variable)
short a_variable;

    The coder (who was inexperienced in C) wanted
to optimize the space needed to save the parameter
passed to the function.  This actually may add to
the memory and time to do conversion between short
and int.

    The worst violation of coding for efficiency
was done in assembler.  The person set a condition
bit inside a subroutine.  After the return the bit
was used in a conditional jump.  Of course, another
programmer saw the subroutine and couldn't under-
stand why there was an unneeded operation in the
subroutine and removed it.

    The time spent coding a project is only about
10%.  The maintenance phase lasts around 50% of a
project.  If the coders write the most readable
code for the maintenance, the entire project cost
can be reduced.

    But there is still a need for optimization.
This should be done after the code is written and
working.  Why?  Because the amount of time spent
in each code segment varies widely.  There is no
reason to optimize the initialization routines if
they are only run once and are fairly fast
already.

    Using prof(1) under UNIX I have always been
suprised at where the time is spent for a given
program.  And using this shows which routines
need to be optimized.  Using a benchmark it was
easy to see that only 10% of the routines were
run 90% of the time.  Some of the results showed
obvious duplication of calculations that were
easy to eliminate.  But instead of trying to
find them by hand, we let the computer show us
where they were.  After changing the obvious
problems, there were many low level optimizations
that were done.  Some included calculating
certain variables once and storing them as
globals while others were to make certain
variables declared as register.  At one point it
became obvious that the semaphore routines
supplied by UNIX took 25-50% of the total time
to do a database retrieve.  (This was solved by
making ownership of relations, and removing the
need to call the semaphore routines.) All through
the optimization process we were aware of what
was the most important code to optimize so we
could, as our boss always put it, "Get the
biggest bang for the buck."

    For less experienced C programmers, try
running prof on a program and see which routines
are actually taking the most amount of time.
Prof will order the output from the most used
routine to the least and give the percentage of
time spent in each routine.  I copied this prof
output from July 87, 1987, p 588, on profilers:

%time cumsecs #call ms/call name
 82.7    4.77               _sqrt
  4.5    5.03   999    0.26 _prime
  4.3    5.28  5456    0.05 _root
  2.6    5.43               _frexp
  1.4    5.51               __doprnt
  1.2    5.57               _write
  ...

This is for a program to compute prime numbers:

root(n)
int n;
{ return (int) sqrt((float) n); }

prime(n)
int n;
{   int i;
    for (i = 2; i <= root(n); i++)
        if (n % i == 0)
            return 0;
    return 1;
}

main()
{   int i, n;
    n = 1000;
    for (i = 2; i <= n; i++)
        if (prime(i))
            printf("%d\n", i);
}

It is interesting to see that the square root
calculation takes this much time for a function
and is not needed to calculate primes.  It was
probably an "optimization" to make the search
for primes quicker.

    In conclusion, I would like to stress that
readability for the maintenance phase should
outweigh the importance of optimizing code as
it is written.  Easy to read code is easier to
maintain, and easier to optimize.

    Paul Schmidt
    Texas Instruments
    PO Drawer 1255, MS 3517
    Johnson City, TN 37605-1255

    mcnc!rti!tijc02!pjs269

leech@tlab1.cs.unc.edu (Jonathan Leech) (11/02/88)

In article <273@tijc02.UUCP> pjs269@tijc02.UUCP (Paul Schmidt	     ) writes:
> Using prof(1) under UNIX I have always been suprised at where the
> time is spent for a given program.  And using this shows which
> routines need to be optimized.  Using a benchmark it was easy to see
> that only 10% of the routines were run 90% of the time.

    There are certainly counterexamples. I have occasionally profiled
the text editor I maintain. No one procedure took more than 3% of
execution time.
--
    Jon Leech (leech@cs.unc.edu)    __@/
    ``It seemed to him that in addition to being beautiful she
      brought out all that was best in him of intellect and soul.
      That is to say, she let him talk oftener and longer than
      any girl he had ever known.''
	- P. G. Wodehouse

bill@twwells.uucp (T. William Wells) (11/02/88)

In article <8775@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
: In article <119@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
: >It's the damnedest thing, but people whose opinions I otherwise
: >respect seem to have this thing about coding efficiently.  They don't
: >like it. Worse, they discourage it.
:
: I think it would be more accurate to say that we want to discourage
: excessive concern with microefficiency to the detriment of other
: important attributes of source code.

Put that way, it is adifficult to argue the other side.  Obviously,
it is not good to write "efficient" code that is not understandable.
I recall writing, early in my programming career, a few lines of
assembly code that did a real neat (and efficient) trick.
Unfortunately, this code was sufficiently tricky that when it came
time to make a minor change to it, I could no longer figure out what
it did! Of course, it got rewritten.

This excessive "efficiency" is unarguably bad. However, anti-
efficiency types (or so they appear) have taken this too far in the
other direction. The kind of thing I do is mostly attention to
detail; the algorithm remains the same, and the broad structure of the
program remains the same. What is different are the coding details.

In fact, what we are talking about is mostly a matter of "style".
However, style does not exist in a vacuum; one chooses a particular
style because it serves some purpose(s). Certainly, a style must be
clear: this implies a consistent method of expressing algorithms
coupled with an eye for laying out the code on the page.  Certainly a
style must be portable: this means leaving out things that don't go
wherever the program might be ported. And just as certainly, a style
should get the job done as efficiently as possible: this means
choosing the details of how the thing is to be coded to be as likely
as possible to be efficient.

These goals aren't entirely consistent; that implies that a coder is
going to have to make a trade-off whose details will depend on the
circumstances.

: >2) "What is more efficient on one machine may well be less efficient
: >   on another." Well, this is a good argument for knowing a lot of
: >   different machines and adapting one's coding style to the relevant
: >   machines, ..
:
: Actually the force of this argument is stronger if you already
: appreciate the value of coding for reasonable behavior on almost
: any C implementation, rather than "optimum" behavior on just one.

Actually, one of the nicenesses of C programming is that reasonable
behaviour is very easy to get: most C operations have a small cost.
However, they do not have equal costs, and sometimes it is reasonable
to make generalizations of the form: "On machines X where efficiency
is of greater concern to me, doing it by method A is usually more
efficient than doing it by B." When such generalizations are possible,
one should take advantage of them.

: Some people really may never port their code outside the first
: system it's compiled for, but many of us do and we simply don't
: have time to fine-tune the code in each environment (unless we
: will personally accrue enough benefit to justify it).  My own
: time is worth much more than any computer's.

Some people have their code ported damn near everywhere. For example,
the spelling software I was responsible for a few years ago runs on
everything from 8086's to IBM-370's.  (This is not to say that there
weren't portability problems: e.g., we had to write a program to
preprocess character strings into \nnn so that it would run on
non-ASCII machines. This was a case where clarity conflicted with
portability.)

However, it was not hard to figure out a reasonable set of
generalizations and then apply them; after all, on which machines
would it matter most how fast the program is: the micros with an
impatient user waiting in front of them, or the mainframes with lots
of horsepower? Of course, I coded it with efficiency on the micros as
the greater concern.

: >3) "Efficient coding makes obscurer programs."
:
: Certainly going overboard with microefficiency tweaks does.

Moderation in all things, especially moderation! :-)

: >     for (i = 0; i < a[j]; ++i) ...
: >into
: >     for (i = 0, ilast = a[j]; i < ilast; ++i) ...
:
: In most cases, the following can be used:
:       for (i = a[j]; --i >= 0; )
: which is more efficient and clearer.

Right. And I use the latter whenever possible; however, it does not
preserve the semantics of the original loop. (I just realized that I
probably should have added a caveat that the transform is invalid if
j is changed in the loop. This applies to both versions, though.)

: (But don't write something like
:       for (p = &a[j]; --p >= a; )
: which is nonportable.)

I'll remember! :-) Eh, Chris?

: >     for (i = 0; i < ALAST; ++i) {
: >             ... A[i];
: >     }
: >
: >     for (ap = A; ap < &A[ALAST]; ++ap) {
: >             ... *ap;
: >     }
: >
: >I write it this second way, as a matter of course, because I thought
: >about it for a while and decided that this was the appropriate way to
: >do it.  I then trained myself into the habit of doing it this way.
:
: This makes a good example of the dangers of overconcern with such
: matters.  There are quite a few compilers that, in many cases, will
: generate much better code for the first method than for the second,
: because they can recognize an opportunity to use vector instructions.
: Others will generate essentially the same code for the two methods.

Why did you say this? I thought I had made it clear that I am well
aware of the differences in compilers and they systems they run on.
I thought I had made it clear that different situations require
different choices in one's code.  However, it appears that I have
failed to communicate. How?  Until I understand where the failure of
communications is, I'll let this one drop.

: Unless the situation is naturally thought of as involving a pointer
: (for example, to a structure member of an array), it may be clearer
: to treat array elements as array elements.

"Naturally?" At least for me, a pointer into an array is neither more
or less "natural" than an index into the array, I'll use either, as
the situation requires.

: >... dividing positive numbers by powers of two should be done with a shift.
:
: No!  Juggling the source code to use bit-oriented operations in an
: arithmetic context not only makes the code less portable and harder
: to maintain, it can also lead to future errors, when for example a
: chunk size is changed to a variable rather than a constant, or to a
: non-power-of-two.

Less portable?

	If a compiler doesn't take my n >> m (n >= 0, m < 16) and do
	the same thing as when dividing it by 2**m, the compiler is
	broken.  Now, there might be some compilers that are broken
	that way, but that makes it an individual decision as to
	whether one panders to the frailties of those compilers. For
	the record, the spelling code I earlier mentioned does this in
	a few places and I've never heard of a problem relating to it.

Harder to maintain?

	There is something there: if one has a number n, one has to
	have a second number representing log2(n); that is a cost.
	But that is a minor one.

	There is also the fact that if the number ceases to be a
	power of two, all references to the log have to be changed,
	but that is mechanical and so another minor cost.

Lead to future errors?

	Only when done wrong. When I do it, it gets done this way:

	#define BLOCK_BITS 9
	#define BLOCK_SIZE (1 << BLOCK_BITS)

	This is a clear signal (which can be reenforced by a comment)
	that BLOCK_SIZE is supposed to be a power of two.  Changing
	it to a variable shouldn't cause any problems: all one needs
	to do is introduce two variables.

	If the size becomes other than a power of two, this is easily
	enough dealt with: it is clear that the program is depending
	on the existence of BLOCK_BITS; an instant's thought will
	tell the maintainer that he has got to do something about all
	references to BLOCK_BITS; such will certainly include all the
	>> BLOCK_BITS stuff.

So, I disagree.

: >Unless, of course, the programmer remembered to COMMENT.
:
: Comments don't necessarily track the code,

Let's see: "Comments don't necessarily track the code, therefore you
shouldn't do something (coding `tricks') where that tracking is
important." Sounds more like an argument for more care in commenting,
not an argument against coding "tricks".  The failures that are
possible in a system which are caused by carelessness by the users of
the system are arguments for teaching the users how to better use
their system, not arguments for discouraging particular uses of the
system. Or even better, for improving the system (but that is a
completely different can of worms. :-)

:                                            and it is unrealistic to
: expect every use of the kind of tricks you advocate to be commented.

Can you say "straw man"?

It is not usually necessary to comment such "tricks". There is
nothing about a counted-down for loop that requires a comment.  There
is nothing about a redundant calculation that gets eliminated that
requires a comment.

There are situations where a comment is desirable; however, even
better is a method like the one I showed for replacing division by
shifting: when the code is changed so that the trick doesn't work,
one is forced to fix the places where the trick is used.

---
Bill
{uunet|novavax}!proxftl!twwells!bill

smryan@garth.UUCP (Steven Ryan) (11/02/88)

>>Realloc has to copy data, so this should be less efficient than just
>>doing another malloc & free.
>
>Err, umm, "realloc" is often used when you have e.g. an array with N

ERRR, UMMMM, we seem to have lost the point here.

It sounds as though someone's guessing what the data structure size should
be, guesses wrong, and has to increase it. If data structure size keeps
getting bumped bit by bit until the problem size is finally determined,
then we've got an O(n) structure which has been copied O(n/increment)=O(n)
times so that the cost of creating just this structure is O(n**2) so that
the overall time is quadratic, at best.

This sounds more like someone's using an inappropriate data structure which
is going to cost far more than any amount tweaking is going to get you.

Tweaking a program gives at best linear improvement.

Fixing the basic algorithm gives at least linear improvement.

--------------------------------------------------------------------------------
Well, gee, how do you allocate an indefinite sized array?

Glad you asked. I stuff it down a stack, each element individually
handcrafted with malloc, until I've reached the end and know exactly
how big it is. Then, IF I need random access, I copy it to an array.
Linear time.

bill@twwells.uucp (T. William Wells) (11/02/88)

One of the things about Usenet that is very unfortunate is that mere
agreement isn't worth commenting on (and wastes bandwidth).  So,
while some of my replies are a bit harsh, do keep in mind that there
is also a lot which I agree with in that posting.

In article <7700@bloom-beacon.MIT.EDU> scs@adam.pika.mit.edu (Steve Summit) writes:
: In article <119@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
: >It's the damnedest thing, but people whose opinions I otherwise
: >respect seem to have this thing about coding efficiently.  They don't
: >like it. Worse, they discourage it.
:
: Some time ago I came to the conclusion that the style vs.
: efficiency debate is another religious war, which I usually try
: to stay out of.  Good style and, I'll admit it, anti-efficiency
: is a personal crusade, though, so I can't help but respond.

But I think you mostly missed my point. One could approximate it
with: "Efficient code IS a matter of style." Most of what I advocate
is choosing a consistent style that is also reasonably efficient.
While you seem to agree with this, you also directed much of your
discussion to code-tweaking, a thing which I agree should be done
after one has discovered a need for greater efficiency.

These are two separate issues, and I wish you'd have addressed the
former and not the latter.

: None of this should be taken as a personal attack against Mr.
: Wells, or anyone else, for that matter.  His points are well
: taken; I simply have a different point of view, going back to
: first principles, as all good religious wars do.

Perhaps we should discuss those "first principles". I had been
composing an article on them,, but I decided that I didn't really
have the time for it. Anyway, here are mine:

    1) Programming is a *utilitarian* art. If the program doesn't do
       the intended job, it is a bad program. This is the overriding
       standard by which one judges a program.

       Portability ranks up here, as a program is portable because it
       can do the job when moved to another machine.  But because
       portability ranks here, there is also a limit on meaningful
       portability: if the program isn't going to be ported then who
       cares if it can be ported?

    2) Programs have to be read and maintained by humans.  This
       implies modularity and a clean and consistent coding style,
       and for procedural programs, a structured program.  This is a
       secondary standard. This is important because it makes it
       easier to fix bugs and to add to the jobs the program can do.

    3) Programs should consume as little resource as possible.
       Programs that consume excess resources waste resources of the
       user, and may also impact other users of a multi-user system.
       In the extreme case, an inefficient program can be unusable.
       This too is a secondary standard.

    4) Programming is a utilitarian *art*.  Be that as it may, and
       while this may contribute to improving the program according
       to the other standards, this is less important than all other
       considerations.  Esthetics is just not that important compared
       to efficiency or maintainability, unless it contributes to one
       or the other.

Any real disagreement?

: >I advocate training oneself to be one who codes efficient programs. I
: >do not mean writing code and then making it more efficient, I mean
: >writing it that way in the first place.
:
: Here I agree with at least some of the words, but not the
: implications.  It happens that I usually write nice, efficient
: programs, but not by resorting to microefficiency tweaks or by
: sacrificing readability in any way.  It is important to employ
: lots of common sense (and, of course, "common sense isn't"); to
: shy away from grotesquely inefficient algorithms; and to partition
: the code so that key sections can be cleanly replaced later if
: efficiency does prove to be an issue.  It isn't important to
: write every last expression and statement in the most
: theoretically blistering way possible.

But who said anything about "ideal"? Not me!  What I want is enough
attention to detail so that the code is reasonably efficient to start
with. A side benefit of this attention to detail is that one often
discovers bugs in the code, thus saving later pain.

: >3) "Efficient coding makes obscurer programs." Well, since most
: >   efficient coding involves minor changes from the inefficient way
: >   of doing things, changes which are mostly a matter of style rather
: >   than of organization...
:
: I don't know what is meant by "most efficient coding."  The
: interpretation "the most efficient coding involves the most minor
: changes" is probably not the one that was intended, although I
: like it, because taken to its extreme it says not to make any
: changes at all.

Oh dear, a semantic problem.  Part of it is in the use of the verb
"code" which, to me, refers to the last phase of writing a program:
the transcription of what you want to do into something the computer
understands. ("Writing a program" is the phase of getting the
damnthing out just before "making it work".) So, to me, coding
doesn't have much lattitude: it presumes that the overall structure
of the program has already been decided, that the major data
structures are mostly decided, etc.

The other part is "changes". This is intended as a comparative, not
as implying that one should actually change ones code in conformance
with my idea of efficient coding. To repeat, one should write it that
way in the first place.

:                  I cannot agree with the more likely
: interpretation.  The minor changes, the replacements of
: multiplies and divides by shifts and the like, are mostly noise
: (both with respect to efficiency and style).

The evidence is against you there: 10-25% improvements are not
"noise".

:                                               The really big
: efficiency hacks, the ones people spend weeks and months on, do
: involve massive organizational changes and wholesale concessions
: to readability and maintainability.

I am not talking about such things, nor do I advocate doing such,
except when the program isn't fast enough.

: >   ...this argument should really be read: "*I*
: >   don't like to or find it hard to read that kind of program, so it
: >   is unclear."
:
: The attitude of most C experts is "*I* don't have any problem
: reading this code; anyone else who considers himself a Real C
: Programmer shouldn't, either."  This attitude is patronizing and
: wrong.

Cut the BS. While I have beliefs that could be approximated by your
nasty remark, they are not the same. You apparently think that it is
proper for a programmer to constrain his style to one that poorly
trained programmers will have no trouble with. This kind of attitude
is not useful. Rather, when we encounter a programmer who can't
understand a good coding style, we should make sure that he gets
taught. That is what we do where I work and so that is how I will
code when working here. As for my personal stuff, I don't care that
the untrained of the world can't read it.

Call that patronizing if you will. I call it having high standards.

:         To borrow a previous argument, "We have to code in the
: real world, not the world of make-believe."  There are plenty of
: people out there who are still learning C,

So teach them right!

:                                            and since C has become
: as popular as it has and C programmers are in demand, many of
: them are working for real companies writing (and maintaining)
: real programs.

Hmmmm. Most programmers come into the real world completely unable to
program. All they have is a little knowledge, and very little
experience. Since they can't write programs well, we should be very
careful that they don't see examples of good programs, since they
might have problems reading them.  That way they won't have to learn
how to write such programs. :-) sort of.

: >Avoid excess function calls. A lot of people have been brainwashed by
: >some modularity gurus into believing that every identifiable "whole"
: >should have its own function. What I have found is that this makes
: >the program LESS maintainable than otherwise.
:
: Here's another religious debate: the structured programming one.

I think you missed the point. Structured programming is essential to
good procedural programming. I am saying that excessive partitioning
not only makes the code less efficient but is actually contrary to the
purposes for which structured programming exists.  So this is a win
all around.

: There are a few applications (and, some believe, a few
: architectures) for which function call overhead is significant,
: but they are infrequent, and in general there should be
: absolutely no stigma attached to a function call.

My particular comment was directed at the following kind of
programming: (This was a real program. Only the names have been
changed....)

string_diddling_function(in, out)
char    *in;
char    *out;
{
	char    temp[MAXSTRING];

	/* comment 1 */

	diddle_1(in, temp);

	/* comment 2 */

	diddle_2(temp, out);

	/* comment 3 */

	diddle_3(out, temp);

	/* comment 4 */

	diddle_4(temp, out);
}

/* Each diddle had about this degree of complexity: */

diddle_1(in, out)
char    *in;
char    *out
{
	while (*in) {
		if (*in == '`') {
			++in;
		} else {
			*out++ = *in++;
		}
	}
	*out = 0;
}

The whole thing was unreadable!

Not only that, but 40% of one program (patgen) was spent in just this
set of routines.  Ugh!

: >:     o   Avoid bit fields, most especially signed ones.
: >Try: don't use them at all, they tend to be nonportable.
:
: This is a side question: what's so unportable about bitfields?

Signedness of the bit fields, for one thing. As I understand it,
compiler writers have chosen to implement them as either signed or
unsigned according to their own whim.  Also, it used to be the case
that a number of compilers either didn't implement them or did them
incorrectly. It may still be.

That, plus the frequent relative inefficiency of these compared to
do-it-yourself bit fields, makes them undesirable.

: >:     o   Use realloc instead of malloc/free pairs. Use calloc instead of
: >:         malloc followed by zeroing each member.
: >Efficient memory allocation is *much* more difficult than this, what
: >you really need to do is to cut down on the number of calls to malloc
: >and free. Of course, that usually means writing some kind of
: >allocation stuff yourself, never an easy task.
:
: Please don't avoid malloc; the alternative is generally
: fixed-size arrays and "lines longer than 512 characters are
: silently truncated."  Please don't roll your own allocation
: routines, again unless you have to; this is the kind of low-level
: dirty work, hard enough to do well, that you should let the lower
: levels (i.e. the standard implementations of malloc) give it
: their best shot.

My original understanding was that someone was advocating something
like changing:

	thing1 = malloc(size1);
	...finish using thing1
	free(thing1);
	thing2 = malloc(size2);
into:
	thing1 = malloc(size1);
	...finish using thing1
	thing2 = realloc(thing1, size2);

I now suspect that I am the only person who read it that way, so most
of what I said is irrelevant. However, what I said about rolling your
own memory allocation still stands, but let me clarify this.

I don't mean writing a malloc replacement, but rather an interface to
malloc. One should always write such an interface, in order to handle
memory allocation failures, unless one want's to check the return
value of each malloc call. Here is an (untesetd) example of what I
mean:

typedef struct MYSTRUCT {
	struct MYSTRUCT *my_next; /* a general link */
	char    *my_field1;
	float   *my_field2;
} MYSTRUCT;

MYSTRUCT *Free_mystruct;        /* A list of currently unused MYSTRUCT's */

/* Everyone gets to malloc through here. It tries to malloc, but if
   that fails, frees whatever is on the free list(s) and then tries
   again. Repeated failure causes the program to exit. */

void *
mymalloc(size)
size_t  size;
{
	void    *ptr;

	while (!(ptr = malloc(size))) {
		if (!Free_mystruct) {
			fprintf(stderr, "You lose: out of memory!\n");
			exit(1);
		}
		while (ptr = Free_mystruct) {
			Free_mystruct = ((MYSTRUCT *)ptr)->my_next;
			free(ptr);
		}
	}
	return (ptr);
}

/* Call here whenever you want a fresh MYSTRUCT. */

MYSTRUCT *
alloc_mystruct()
{
	register MYSTRUCT *ptr;

	if (ptr = Free_mystruct) {
		Free_mystruct = ptr->my_next;
	} else {
		ptr = mymalloc(sizeof(MYSTRUCT));
	}
	ptr->my_next = 0;
	ptr->my_field1 = 0;
	ptr->my_field2 = 0;
	return (ptr);
}

Now, I can already hear the screams of "Unclean, unclean!!!!" from
those who don't like my style of coding. Let's save bandwidth and not
flame, OK?

:                      Why does calloc exist?  Of what use is it?
: Why has ANSI legitimized it?  In principle, it is equally useless
: for clearing structures that contain floating-point fileds.

Oh yes. I forgot about floating point. And I suppose the reason that
ANSI left it around is the number of existing programs that use it.
Me, I never have and never will.

: >Unless, of course, the programmer remembered to COMMENT.
:
: If the code reads
:
:       a &= 3;         /* really a %= 4 */
: or
:       a &= 3;         /* really a %= HASHSIZE */
:
: and I do a global replace of 4, or re#define HASHSIZE, the
: comment may not help.

Yes, but writing an explicit constant is bad to start off with. It
should be:

#define HASHSIZE 4              /* A power of two, or else! */

	a &= HASHSIZE - 1;

:              find code size far more frequently relevant in
: practice (among other things, because of the aforementioned
: pdp11).  Remember that the classic tradeoff is between time and
: space, so the fancy efficiency hacks often make the code larger.

I believe that the "classic trade off" has almost nothing to do with
coding. Except when using data to replace code, faster code is
usually smaller and smaller code is usually faster.  This is good
news for coders!

: I might also point out that the marketplace generally rewards the
: product that comes out first, so if you can compress your
: development schedule by using clean, straightforward design and
: eschewing time-consuming and bug-prone efficiency tweaking,

There you go again, attacking the wrong issue. Grrrrrr.

: While these considerations are real, they should be viewed as
: unfortunate and temporary limitations which will be resolved,
: hopefully soon enough, at the root level, and not something which
: we resign ourselves to working around forever, and which we get
: so used to working around that the workarounds become part of the
: language, persisting well beyond their need.  These are what Fred
: Brooks calls "accidental problems," and every programmer minute
: and line of code spend catering to them is not being spent on
: real problems.

Keep dreaming. It is still the case that our customers don't like our
products when they consume over 64K, even though many (most?) machines
have *lots* more memory. It will always be the case that there is
never enough to go around.

: I'm not going to try to second-guess all of the replies I will
: undoubtedly get from the efficiency aficionadoes out there, but
: I will mention two things.

Click, whoosh!!!!! :-)

: I keep saying "don't do <something> unless you have to," by which
: I mean after actual experiments on prototype code demonstrate
: that there is a problem.  The attitude of "don't worry about
: efficiency until later" is frequently denigrated as leading to
: unsalvageably inefficient code, because trying to patch in some
: efficiency later is tantamount to a rewrite.  Although this can
: be true, the solution is to teach people good, responsible
: programming practices early, avoiding gratuitous and unnecessary
: inefficiency, without teaching them to "optimize too early."

What in *hell* do you think I am advocating?  It is very frustrating
to see myself being misunderstood (ignored?) so thoroughly.

: Responsible programming practices are just what the articles I am
: reacting to are trying to formulate, and all I'm trying to do is
: to draw the line a little more finely between what's reasonable
: and what's overboard.

But I think you rather failed. You fairly consistently attacked
efficiency tweaking, and with arguments that are mostly relevant to
that discussion. But, as you said, I am advocating what you called
"responsible programming", and I'd have much rather seen discussion
on that subject.

Instead, I get what seems to be a disagreement with my position, but
what is really a disagreement with a straw man.  How can I answer
that? What can we learn from that?

:                        My second point, and the reason I'm taking
: all of this time and space replying, is that people who are
: learning programming (and we're all still learning programming)
: are much more impressionable than you might think.  There's
: nothing wrong with this; it's actually refreshing given that it
: is often supposed that people stop learning at around age 20.
: But it does mean that if you unilaterally tell a reasonably green
: programmer to use pointers instead of arrays, he'll spend months
: having lots of frustrating problems with pointers, and likely end
: up hating them and the language in general.

Did I anywhere do anything like that? I believe that I prefaced my
remarks with an awful lot of "but consider your particular
circumstances first".

Sigh. I *hate* being misunderstood.

---
Bill
{uunet|novavax}!proxftl!twwells!bill

ok@quintus.uucp (Richard A. O'Keefe) (11/02/88)

In article <1709@garth.UUCP> smryan@garth.UUCP (Steven Ryan) writes:
[the context is realloc()]

>It sounds as though someone's guessing what the data structure size should
>be, guesses wrong, and has to increase it. If data structure size keeps
>getting bumped bit by bit until the problem size is finally determined,
>then we've got an O(n) structure which has been copied O(n/increment)=O(n)
>times so that the cost of creating just this structure is O(n**2) so that
>the overall time is quadratic, at best.

Not so.  The standard technique is to increase the size by a MULTIPLICATIVE
factor, not an additive increment.  The best factor is data-dependent; I
tend to use 1.5 because that's what InterLisp used, 2 is pretty popular.
The total cost in this case remains O(N), N being the final size.

rkl1@hound.UUCP (K.LAUX) (11/03/88)

In article <136@twwells.uucp>, bill@twwells.uucp (T. William Wells) writes:
| In article <8775@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
| : In article <119@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
| 
| : >... dividing positive numbers by powers of two should be done with a shift.
| :
| : No!  Juggling the source code to use bit-oriented operations in an
| : arithmetic context not only makes the code less portable and harder
| : to maintain, it can also lead to future errors, when for example a
| : chunk size is changed to a variable rather than a constant, or to a
| : non-power-of-two.
| 
| Less portable?
| 
| 	If a compiler doesn't take my n >> m (n >= 0, m < 16) and do
| 	the same thing as when dividing it by 2**m, the compiler is
| 	broken.  Now, there might be some compilers that are broken
| 	that way, but that makes it an individual decision as to
| 	whether one panders to the frailties of those compilers. For
| 	the record, the spelling code I earlier mentioned does this in
| 	a few places and I've never heard of a problem relating to it.
| 

	I'm suprized that noone has questioned the validity of shifting
instead of dividing by (powers of) 2.

	What about the difference between Logical and Arithmetic shifts?
If you want to divide, then divide!  A lot of compilers are smart enough
to implement the divide as a shift, but only where appropriate.

	I *did* notice the condition of dividend being positive, but now
you have to *guarantee* that it will always be positive.  Also, by
implication, the dividend is a *signed* quantity.  You can get into
trouble if it gets changed to an *unsigned* quantity (like when 16K isn't
enough).

	As for the 'compiler being broken' if it doesn't do the same thing
for a shift and dividing, this is not true.  In one case, you only told
it to shift, the other to do a divide:  they made be *equivalent*, but
they certainly are not *equal*.

	So, please, if you *functionally* require a divide by (powers of) 2,
code it as a divide and let the compiler *implement* it (maybe optimization
needs to be turned on).

--rkl

gwyn@smoke.BRL.MIL (Doug Gwyn ) (11/03/88)

In article <137@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
>#define HASHSIZE 4              /* A power of two, or else! */

To continue my complaint against using bitwise operators where arithmetic
is called for, let me point out that most reasonable hashing schemes work
best of the hash table size is NOT a power of two.  Now, if you had used
arithmetic instead of bit-diddling throughout your hashing code, most
likely a programmer could change HASHSIZE to some useful number like 113
and that would be all it would take to improve the hashing scheme.  On
the other hand, your code would force him to either use 128 (and obtain
suboptimal performance), or else go through the code and try to put it
back in the shape it should have had from the outset.

Again, this is a case that any decent compiler would have been able to
handle just as well if you had used division and remainder operators.
The trickery is not only unnecessary, but (as I said previously) it gets
in the way of code reliability and maintainance.

bill@twwells.uucp (T. William Wells) (11/03/88)

In article <273@tijc02.UUCP> pjs269@tijc02.UUCP (Paul Schmidt        ) writes:
:     When I was optimizing I found some horrendous
: "efficient coding" practices that were used that
: made the code either less managable, less efficient
: or both.
:
:     Take, for example, my favorite:
:
: some_function(a_variable)
: short a_variable;

Here you are just demonstrating my point: had the programmer been
trained to write efficient code, he'd have known that, in general,
one doesn't declare short arguments (or float or char, for that
matter).

I'll repeat myself: I am not advocating optimizing one's programs, I
am advocating learning the coding techniques which generally result
in efficient code and applying them consistently.

:     The coder (who was inexperienced in C) wanted
: to optimize the space needed to save the parameter
: passed to the function.  This actually may add to
: the memory and time to do conversion between short
: and int.

Actually, declaring the thing short would not have been likely to
save any stack space: the caller almost always has to push an int.
(Except in ANSI C when there is a prototype, or in the unusual case
that the compiler can do inter-function optimizations.)

:     The worst violation of coding for efficiency
: was done in assembler.  The person set a condition
: bit inside a subroutine.  After the return the bit
: was used in a conditional jump.  Of course, another
: programmer saw the subroutine and couldn't under-
: stand why there was an unneeded operation in the
: subroutine and removed it.

Sounds to me like a documentation error: that returned bit is part of
the interface and should have been documented as such. Thus the error
you use as an example of bad coding for efficiency's sake is nothing
of the sort.

:     The time spent coding a project is only about
: 10%.  The maintenance phase lasts around 50% of a
: project.  If the coders write the most readable
: code for the maintenance, the entire project cost
: can be reduced.

Here we get the same old argument, rehashed: "efficient code is
unreadable". It is not necessarily true.  And in many cases, the
claim of unreadability is merely a demonstration that the reader does
not make it a practice to read that kind of code, rather than a valid
claim that the code is inherently unreadable.

I'll go even further: while there is code that is both unreadable and
efficient, the unreadability is more likely a consequence of the
programmer's inability or unwillingness to express himself properly
than a consequence of the code being written efficiently.

I'll even go so far as to say that efficiency antagonists compound
this.  By setting up the false dichotomy of efficient vs. readable
code, they convince programmers that they can either write efficient
code or readable code, but not both.  This merely makes people who
believe in efficiency give up the attempt to make their efficient
code readable. And, of course, their code is unreadable.

My own code is both efficient and readable. The former I know because
of testing; the latter because of feedback from people who have paid
money for it. So, I know that one can have it both ways.

: This is for a program to compute prime numbers:
:
: [code omitted, except for one line:]
:     for (i = 2; i <= root(n); i++)
:
: It is interesting to see that the square root
: calculation takes this much time for a function
: and is not needed to calculate primes.  It was
: probably an "optimization" to make the search
: for primes quicker.

It is more interesting to note that the simple discipline of avoiding
computing a thing more than once would have been sufficient to get
rid of most of the calls. Had the for loop been written

	imax = root(n);
	for (i = 2; i <= imax; i++)

two benefits would have been had. First, no time would have been
wasted waiting for or optimizing the thing. And second, the next
person who read the code wouldn't have to figure out that root(n) is
a loop invariant. (Trivial in this case, but not so in something more
complicated.)

:     In conclusion, I would like to stress that
: readability for the maintenance phase should
: outweigh the importance of optimizing code as
: it is written.  Easy to read code is easier to
: maintain, and easier to optimize.

Well, you said it, but you utterly failed to demonstrate it.  All
that you have shown are examples of where bad programming leads to
unreadable code as well as inefficient code.

---
Bill
{uunet|novavax}!proxftl!twwells!bill

smryan@garth.UUCP (Steven Ryan) (11/03/88)

>[the context is realloc()]
>
>Not so.  The standard technique is to increase the size by a MULTIPLICATIVE
>factor, not an additive increment.  The best factor is data-dependent; I
>tend to use 1.5 because that's what InterLisp used, 2 is pretty popular.
>The total cost in this case remains O(N), N being the final size.

However, this makes the space cost O(1.5**n), exponential.

seanf@sco.COM (Sean Fagan) (11/03/88)

In article <273@tijc02.UUCP> pjs269@tijc02.UUCP (Paul Schmidt        ) writes:
>
>    After working for a year to optimize a DBMS I
>have some comments on writing efficient code.
>    Take, for example, my favorite:
>some_function(a_variable)
>short a_variable;
>    The coder (who was inexperienced in C) wanted
>to optimize the space needed to save the parameter
>passed to the function.  This actually may add to
>the memory and time to do conversion between short
>and int.

If I were to look at that, I would say that the coder used a short because
he/she wanted to document the fact that the value was not expected to
overflow a short. (is anybody following me?  if so, could you explain it to
me? 8-)  Here's another shot:)  I.e., "I don't expect the value to be larger
than 32k-1 [on most 32-bit machines, for example]."  It's what I would do.

In the rest of the article, he only says one thing I really agree with:

>    But there is still a need for optimization.
>This should be done after the code is written and
>working.  Why?  Because the amount of time spent
>in each code segment varies widely.

Also, Paul advocates the use of prof.  I agree (if anyones interested,
there's a story I have about how profiling enabled someone to cut the
execution time of a program from 9+ CPU hours on a Cyber [a *very* fast
number cruncher] to about 2-5 minutes [on the same machine, of course]).

Most programmers (in C) that I know tend to do some "microoptimizations" as
they write (i.e., "a&0xf" instead of "a%16" type of thing).  Afterwards, I
(and they) tend to look at the program, try to understand what I wrote, and
see how I can improve it (which isn't always easy).  For me, this process is
modified by how the compiler optimizes, and what the machine architecture
I'm on dictates (I admit that I don't necessarily write portable, optimal
code.  Tough. 8-)).

Basicly, what I'm trying to say is that not all microoptimization is bad.

>    Paul Schmidt
>    mcnc!rti!tijc02!pjs269

-- 
Sean Eric Fagan  | "Engineering without management is *ART*"
seanf@sco.UUCP   |     Jeff Johnson (jeffj@sco)
(408) 458-1422   | Any opinions expressed are my own, not my employers'.

djones@megatest.UUCP (Dave Jones) (11/03/88)

Okay, I'll wade into the fray.  But only up to my, er, toenails this time.
Last time I ventured into a religious war, I was burned at the
metaphorical stake,  but I never converted by gum!  No doubt this 
will have the same outcome.


From article <8819@smoke.BRL.MIL>, by gwyn@smoke.BRL.MIL (Doug Gwyn ):
> In article <137@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
>>#define HASHSIZE 4              /* A power of two, or else! */
> 
> To continue my complaint against using bitwise operators where arithmetic
> is called for, let me point out that most reasonable hashing schemes work
> best of the hash table size is NOT a power of two.

Perhaps most, but certainly not all.  My favorite *requires* a power
of two.  One exception disproves the rule.  But aren't we straying away
from the subject?  The subject is whether it is EVER okay to use >> to
implement division by a power of two, not whether or not it is best, in some 
hypothetical case, to divide by a power of two or a by a prime number.

> ...

> Again, this is a case that any decent compiler would have been able to
> handle just as well if you had used division and remainder operators.

True enough.  But what if you are programming for the new Banana 8000,
and you don't have access to a "decent compiler", because there aren't
any?  Or, more to the point,  what if the power of two you are dividing
by is not a constant?  

I've got a hash-table package that has been working just fine, 
thankyewverymuch -- and fast as blazes -- in lots of applications written
by lots of programmers, for about three years now.  I expect it
to live on indefinately. It uses a quick rehashing scheme, not chaining. 
It automatically doubles the size of the table when things get crowded.
And yet entries can be efficiently removed on demand. It is the fastest
hashing algorithm I know of. 

The speed of the division makes a difference.

The table size is not a constant, but it's always a power of two.  
Has to be.  I used a shift to divide by the power of two, and I'm not 
about to make it slower because of religious dogma.

> The trickery is not only unnecessary, but (as I said previously) it gets
> in the way of code reliability and maintainance.

What trickery?

ok@quintus.uucp (Richard A. O'Keefe) (11/03/88)

In article <1718@garth.UUCP> smryan@garth.UUCP (Steven Ryan) writes:
>>[the context is realloc()]
>>
>>Not so.  The standard technique is to increase the size by a MULTIPLICATIVE
>>factor, not an additive increment.  The best factor is data-dependent; I
>>tend to use 1.5 because that's what InterLisp used, 2 is pretty popular.
>>The total cost in this case remains O(N), N being the final size.
>
>However, this makes the space cost O(1.5**n), exponential.

No such thing.  The space cost is O(N).

What we're talking about here is a technique for maintaining dynamically
sized data structures.  Just to keep it simple:

typedef <whatever> element;

struct dynarray
    {
	unsigned limit;		/* how many cells available */
	unsigned count;		/* how many in use */
	element *table;		/* the memory block */
    };

int init_dyn_array(struct dynarray *p; unsigned initalloc)
    {
	element *table =
	    (struct element *)malloc(initalloc * sizeof *table);

	if (table == NULL) return -1;
	p->limit = initalloc, p->count = 0, p->table = table;
	return 0;
    }

element *dyn_array_elt(struct dynarray *p; int index)
    {
	return index < 0 || index >= p->count ? NULL : &(p->table[index]);
    }

int push_dyn_array(struct dynarray *p; element new_value)
    {
	if (p->count == p->limit) {
	    unsigned newlimit = p->limit*2;
	    element *table =
		(struct element *)realloc(p->table, newcount * sizeof *table);

	    if (table == NULL) return -1;
	    p->limit = newlimit, p->table = table;
	}
	p->table[p->count++] = element;
	return 0;
    }

These routines return -ve or NULL to indicate error.

It is easy to see that if you build a flexible array by doing
	struct dynarray flex;

	if (init_dyn_array(&flex, 1)) abort();
	...
	for (...) {
	    ...
	    if (push_dyn_array(&flex, x)) abort();
	    ...
	}
then the space required to hold N elements is at most 2*N*sizeof (element).
This is O(N), not O(1.5**N) as Steve Ryan claims.

If we assume that the cost of realloc(o,x) is at most O(x + size of o(x),
it is straightforward to show that the time cost is also O(N) (basically,
the latest doubling always dominates what has preceded it).

chase@Ozona.orc.olivetti.com (David Chase) (11/03/88)

In article <1709@garth.UUCP> smryan@garth.UUCP (Steven Ryan) writes:
>It sounds as though someone's guessing what the data structure size should
>be, guesses wrong, and has to increase it. If data structure size keeps
>getting bumped bit by bit until the problem size is finally determined,
>then we've got an O(n) structure which has been copied O(n/increment)=O(n)
>times so that the cost of creating just this structure is O(n**2) so that
>the overall time is quadratic, at best.

First estimate is size k, too small, so we double it (etc) finally
reaching size k*2^n.  How many bytes have we copied?  We've copied

        k + k * 2 + ... + k * 2^(n-1) = k * (2^n - 1).

This is linear in the final size.  We also did n allocations, but n is
proportional to the log of the size (and bounded by 32 on most
machines that I use) so we don't worry about the time spent
allocating.

"Efficient coding" is indeed harmful if you can't figure out the
complexity of your algorithms.

David

gregg@ihlpb.ATT.COM (Wonderly) (11/03/88)

From article <1709@garth.UUCP>, by smryan@garth.UUCP (Steven Ryan):
>>>Realloc has to copy data, so this should be less efficient than just
>>>doing another malloc & free.

UMMMM, realloc() only moves things when it has to.

>>
>>Err, umm, "realloc" is often used when you have e.g. an array with N
> 
> ERRR, UMMMM, we seem to have lost the point here.
> 
> It sounds as though someone's guessing what the data structure size should
> be, guesses wrong, and has to increase it. If data structure size keeps
> getting bumped bit by bit until the problem size is finally determined,
> then we've got an O(n) structure which has been copied O(n/increment)=O(n)
> times so that the cost of creating just this structure is O(n**2) so that
> the overall time is quadratic, at best.
>
> .....
>
> Well, gee, how do you allocate an indefinite sized array?
> 
> Glad you asked. I stuff it down a stack, each element individually
> handcrafted with malloc, until I've reached the end and know exactly
> how big it is. Then, IF I need random access, I copy it to an array.
> Linear time.

That is moving all of the data twice, but you are still doing more work than
might be necessary for the simple case.  The cost realloc()ing an array can be
decreased by using incremental resizing where the increment is >>> 1.

/*
 *	addstr()
 *
 *	Add a dynamically allocated string to a dynamically allocated array.
 *	Input is the existing array pointer, the number of entries already
 *	allocated, the entries used (current index) and the string to add.
 */

#define	BIGINC	100

char **addstr (arr, acnt, cnt, str)
	register char **arr, *str;
	register int *acnt, *cnt;
{
	register char *t;

	/*  If space exhausted get some more.  */

	if (*cnt == *acnt) {

		/*  If space allocated, realloc else malloc.  */

		if (!acnt)
			arr = (char **)realloc (arr, (*acnt += BIGINC) * sizeof (char *));
		else
			arr = (char **)malloc ((*acnt += BIGINC) * sizeof (char *));
	}

	/*  Allocate space for the string.  */

	t = (char *) malloc (strlen (str) + 1);
	strcpy (t, str);
	arr[(*cnt)++] = t;
	return (arr);
}

Now, only if there are more than BIGINC entries will I have to realloc the
array, and better yet, I didn't have to move the strings.  And, only
every BIGINC entries will I have to move the pointers...  This method is
extremely STRAIGHT FORWARD, and easy to understand.  It does not involve
lots of tricky programming to try and get around having to move the strings.

-- 
Gregg Wonderly
AT&T Bell Laboratories                   DOMAIN: gregg@ihlpb.att.com
IH2D217 - (312) 979-2794                 UUCP:   att!ihlpb!gregg

ark@alice.UUCP (Andrew Koenig) (11/04/88)

In article <2730@hound.UUCP>, rkl1@hound.UUCP (K.LAUX) writes:
> 	I'm suprized that noone has questioned the validity of shifting
> instead of dividing by (powers of) 2.
> 
> 	What about the difference between Logical and Arithmetic shifts?
> If you want to divide, then divide!  A lot of compilers are smart enough
> to implement the divide as a shift, but only where appropriate.

This correctly picks up a simple problem, but misses a deeper one:
even if you use an arithmetic shift, shifting right one bit is
still not equivalent to dividing by 2 if the dividend is negative,
at least on machines that round the quotient toward zero.

Example: on a 32-bit 2's complement machine, -1 is represented as
0xFFFFFFFF.  Shifting right one bit arithmetically still gives
0xFFFFFFFF.  Dividing -1 by 2, though, gives 0.

It is true, however, that shifting an unsigned integer right is
equivalent to dividing it by a power of 2.  I would expect a good
C compiler to recognize this equivalence and replace a division
by a corresponding shift where applicable.

Section 7.5 (page 90) of `C Traps and Pitfalls' has more to say
about shifting and division.
-- 
				--Andrew Koenig
				  ark@europa.att.com

bill@twwells.uucp (T. William Wells) (11/04/88)

In article <8819@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
: In article <137@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
: >#define HASHSIZE 4              /* A power of two, or else! */
:
: To continue my complaint against using bitwise operators where arithmetic
: is called for, let me point out that most reasonable hashing schemes work
: best of the hash table size is NOT a power of two.

But there are also some that don't.

:                                                     Now, if you had used
: arithmetic instead of bit-diddling throughout your hashing code, most
: likely a programmer could change HASHSIZE to some useful number like 113
: and that would be all it would take to improve the hashing scheme.

But on quite a few of the hashing schemes that use a power of two,
you *can't* change the number to anything other than a power of two.
It breaks the algorithm.

Actually, I almost agree with you here: if the hash method were not
one where the size had to be a power of two, it would be incorrect to
use bitwise operators. However, the mistake would have been in making
the requirement that a tunable parameter which is not naturally
restricted to a power of two be restricted to be a power of two.  That
mistake would then be compounded by taking advantage of the bogus
restriction. (Ack, I *hate* subjunctives!)

Formulated as a general principle: if an operand in a multiply or the
divisor in a divide or mod (with the other operand being known to be a
positive number) is one which in the problem being solved is
naturally a power of two, it is reasonable to use bitwise operators to
implement the operation, provided that a #define is used to define
the number and (when needed) its log base 2.

---
Bill
{uunet|novavax}!proxftl!twwells!bill

news@rosevax.Rosemount.COM (News administrator) (11/04/88)

[profile of prime calculation]
>%time cumsecs #call ms/call name
> 82.7    4.77               _sqrt

[in code]
>    for (i = 2; i <= root(n); i++)

[note: root() calls sqrt()]

sqrt() shouldn't dominate your profile so much after you take it out
of the loop.  This isn't fortran; root() is called on every iteration.

True, some optimizations are maintenance pessimizations, but
most C compilers can't tell when function calls can be optimized out.
Thus the call for a "pure function" keyword (a function whose value is
the same for the same arguments, with no side effects).

>    Paul Schmidt

Merlyn LeRoy

desnoyer@Apple.COM (Peter Desnoyers) (11/04/88)

In article <1718@garth.UUCP> smryan@garth.UUCP (Steven Ryan) writes:
>>[the context is realloc()]
>>
>>Not so.  The standard technique is to increase the size by a MULTIPLICATIVE
>>factor... I tend to use 1.5 ...
>>The total cost in this case remains O(N), N being the final size.
>
>However, this makes the space cost O(1.5**n), exponential.

No, max size <= 1.5N = O(N). There's a big difference between linear
and exponential.

				Peter Desnoyers

jss@hector.UUCP (Jerry Schwarz) (11/04/88)

In article <624@quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes:
>
>What we're talking about here is a technique for maintaining dynamically
>sized data structures.  Just to keep it simple:

It seems that way.  So since this discussion has very little to do with
C at this point perhaps it should be moved elsewhere.

Jerry Schwarz

ps@celerity.UUCP (Patricia Shanahan) (11/04/88)

In article <8819@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
>In article <137@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
>>#define HASHSIZE 4              /* A power of two, or else! */
>
>To continue my complaint against using bitwise operators where arithmetic
>is called for, let me point out that most reasonable hashing schemes work
>best of the hash table size is NOT a power of two.  Now, if you had used
>arithmetic instead of bit-diddling throughout your hashing code, most
>likely a programmer could change HASHSIZE to some useful number like 113
>and that would be all it would take to improve the hashing scheme.  On
>the other hand, your code would force him to either use 128 (and obtain
>suboptimal performance), or else go through the code and try to put it
>back in the shape it should have had from the outset.
>...


Although I agree with most of what you are saying, it is not safe to assume
that hashing schemes necessarily work better for HASHSIZE 113 than for 128.
I did some experiments to select a hashing function for use in an assembler.
I extracted all identifiers from several assembly language programs and
measured the symbol table look up time and other statistics for each of the
hash schemes that I was considering.

Hashing with a prime size reduced the number of collisions.  Hashing with a
power of two size reduced the total time to do the symbol table management.

The reason was that the extra time to do a prime division compared to a bit
masking outweighed the small savings due to reduced search length.  This is
an empirical result, applicable only to the machine for which I was coding,
and the type of mix of input strings that resulted from extracting the
identifiers. 

I agree that micro-efficiency should not be considered during initial coding
because of this type of experience. To do a good job of performance
implementation for a particular machine requires a lot more work than
appears on the surface. Many trade-offs require careful analysis or
measurement for the actual machine. It is usually not worth doing for most
functions.

	ps
	(Patricia Shanahan)
	uucp : ucsd!celerity!ps
	arpa : ucsd!celerity!ps@nosc
	phone: (619) 271-9940

tim@crackle.amd.com (Tim Olson) (11/04/88)

In article <8386@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
| This correctly picks up a simple problem, but misses a deeper one:
| even if you use an arithmetic shift, shifting right one bit is
| still not equivalent to dividing by 2 if the dividend is negative,
| at least on machines that round the quotient toward zero.
| 
| Example: on a 32-bit 2's complement machine, -1 is represented as
| 0xFFFFFFFF.  Shifting right one bit arithmetically still gives
| 0xFFFFFFFF.  Dividing -1 by 2, though, gives 0.
| 
| It is true, however, that shifting an unsigned integer right is
| equivalent to dividing it by a power of 2.  I would expect a good
| C compiler to recognize this equivalence and replace a division
| by a corresponding shift where applicable.

A good compiler can also recognize a signed integer divided by a power
of 2, and use the following trick:

	;f(int a, unsigned b)
	;{
	;       int i, j;
	;
	;       i = a/2;
	        srl     gr121,lr2,31	; copy sign bit of a into lsb
	        add     gr121,gr121,lr2	; add sign bit to a
	        sra     gr120,gr121,1	; now we can divide by 2 (sra!)
	;       j = b/2;
	        srl     gr121,lr3,1	; b is unsigned -- just shift
	;       return i+j;
	;}

	-- Tim Olson
	Advanced Micro Devices
	(tim@crackle.amd.com)

ok@quintus.uucp (Richard A. O'Keefe) (11/04/88)

In article <10804@ulysses.homer.nj.att.com> jss@hector.UUCP (Jerry Schwarz) writes:
>In article <624@quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes:
>>What we're talking about here is a technique for maintaining dynamically
>>sized data structures.  Just to keep it simple:
>
>It seems that way.  So since this discussion has very little to do with
>C at this point perhaps it should be moved elsewhere.

I didn't intend to say any more about this, but I can't let "very little
to do with C" stand.  In any halfway decent programming language,
flexible data structures should just be _there_ as part of the language,
without requiring the programmer to kludge them up using unsafe operations
(malloc(), free(), and realloc() are demonstrably unsafe:  hacks to catch
memory leaks have been discussed here several times).  Given that C's
claim to fame is giving you all the rope you need, and given that we
have posters who evidently didn't _know_ how to maintain flexible arrays
in C, I think there was some point in the discussion, and especially some
point in exhibiting simple code to illustrate the technique.

If we come right down to it, most of the raving that's been going on about
efficiency hacks (desirable or not) was old news to Fortran programmers
10 years ago.  Should that discussion be moved elsewhere?  Well, I'm using
my 'n' key a lot, but evidently there are people reading this group who
care about it.  It's their newsgroup too.

rob@raksha.eng.ohio-state.edu (Rob Carriere) (11/05/88)

In article <1718@garth.UUCP> smryan@garth.UUCP (Steven Ryan) writes:
>>[the context is realloc()]
>>
>>Not so.  The standard technique is to increase the size by a MULTIPLICATIVE
>>factor, not an additive increment.  The best factor is data-dependent; I
>>tend to use 1.5 because that's what InterLisp used, 2 is pretty popular.
>>The total cost in this case remains O(N), N being the final size.
>
>However, this makes the space cost O(1.5**n), exponential.


Would someone explain where I am being dense?  As far as I can see,
you need to realloc 

    ln N
k = -------
    ln 1.5m

times, where m is the size of the initial allocation.  Is this not
O(ln N)?

Rob Carriere

terry@wsccs.UUCP (Every system needs one) (11/05/88)

In article <7700@bloom-beacon.MIT.EDU>, scs@athena.mit.edu (Steve Summit) writes:
> In article <119@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
> >It's the damnedest thing, but people whose opinions I otherwise
> >respect seem to have this thing about coding efficiently.  They don't
> >like it. Worse, they discourage it.

Here here.

> Some time ago I came to the conclusion that the style vs.
> efficiency debate is another religious war, which I usually try
> to stay out of.  Good style and, I'll admit it, anti-efficiency
> is a personal crusade, though, so I can't help but respond.

Why is it that people assume good style and efficiency is mutually exclusive?

This is a PC mentality.  Good style is appropriate commenting, small program
flow jumps (less than 2 pages, preferrably very much shorter than that),
and portability.

Style is often linked to which editor you use.  For a UNIX environment, a
good function declaration would be:

type
function()
parameter declarations;
{
	code
	code
	code
}

This allows searching for function names in a program with grep ^function *.c;
it allows use of the [ and ] vi commands to jump through blocks of code;
the type declarations before the function allow crosreferencing with a simple
sgrep/awk combination.

Unions should be avoided, as well as non-pre-aligned structures.  If you must
use a structure for othere than internal data representation, it should be
aligned on longword boundries.

if statements should look like:

	if( expression operator expression)
rather than
	if (expressionoperatorexpression)

which can break old compilers with either the space after the if or
the runniong together of tokens such as

	=& and = &, =* and = *, etc.

expressions where you are unsure of precedence should be paranthesized; if
you are unsure of precedence, relearn your C.

> I simply have a different point of view, going back to
> first principles, as all good religious wars do.  When caught
> between a rock and a hard place, when something's got to give,
> some people shrug their shoulders and sacrifice style,
> modularity, or portability.  I am prepared to sacrifice
> efficiency.  (Most of the time I can have my cake and eat it too,
> and I have any number of general techniques for doing so, which I
> will probably defer to a separate posting so that those who are
> sensibly ignoring these diatribes can still see them.)

usually, using an oblique "style standard" is what offends the portability
gods, not efficiency.

Being prepared to sacrafice efficiency on the alter of style is no way to
maintain a market share.  If it takes gross code to do something fast (code
is usually called gross if it can't be understood by the reader, which is more
a failing of the reader than a failing of the coder) you write gross code.
The only thing to sacrifice efficiency to is the God of portability, and
that's only if it isn't generally portable; if it can be made efficient and
portable by two methods, ...well, that's why God invented #ifdef

> >I advocate training oneself to be one who codes efficient programs. I
> >do not mean writing code and then making it more efficient, I mean
> >writing it that way in the first place.
> 
> Here I agree with at least some of the words, but not the
> implications.  It happens that I usually write nice, efficient
> programs, but not by resorting to microefficiency tweaks or by
> sacrificing readability in any way.  It is important to employ
> lots of common sense (and, of course, "common sense isn't"); to
> shy away from grotesquely inefficient algorithms; and to partition
> the code so that key sections can be cleanly replaced later if
> efficiency does prove to be an issue.  It isn't important to
> write every last expression and statement in the most
> theoretically blistering way possible.

Unless you are competing for a customer.  Run cu for a terminal session
or run uucp for a transfer and then run our stuff.  Blisteringly fast
means a savings in real dollars to a customer.  Someone at Sun voiced
this as a pro-tempe' justification for writing the SPARC compiler so it
compiles benchmarks instead of code.

> >I have heard three main counterarguments to this:
> >1) "The compiler ought to handle that, so you shouldn't bother with
> >   it." What nonsense! We have to code in the real world, not the
> >   world of make-believe and ought-to.
> 
> Many of the more egregious examples of source-level
> microefficiency tweaking are, in fact, routinely handled by
> today's (and even yesterday's) compilers.  Consider replacing
> power-of-two multiplies by left shifts, a textbook example.
> I just booted up my pdp11 (I am not making this up), and its
> optimizer knows how to shift left when I multiply by two.  (To be
> sure, not all newer compilers have been better.)

It is sheer stupidity to depend on the supposed contents of a black box; for
instance, a compiler.  This generates non-portable and lazy coding practices

"aw... the compiler'll make it fast..."

> >3) "Efficient coding makes obscurer programs." Well, since most
> >   efficient coding involves minor changes from the inefficient way
> >   of doing things, changes which are mostly a matter of style rather
> >   than of organization...

I disagree here; proper style is not necessarily efficiency; A program
generator can do the work of 5 ordinary programmers; no code generator can
replace one gifted programmer; that's why assembly language is still in
use.

> The really big
> efficiency hacks, the ones people spend weeks and months on, do
> involve massive organizational changes and wholesale concessions
> to readability and maintainability.

readability, yes; maintainability, not necessarily.  But I submit that this
loss of readability lies either in the lack of documentation (primarily in
the form of appropriate/timely comments) or skill on the part of the reader.

Ability to understand others code is the difference between a programmer and
a person who can program.  Writing code for idiots is only good if you are
an idiot and can do no better, or if you are willing to hire idiots.  This
type of "communism of coding", where the tradeoff is always made for the
less gifted programmer is what is currently threatening to move a lot of
the more exciting/profitable/innovative coding offshore.  A person who can
program can not always read a programmers code.  Tough.  Hire a programmer
instead of a code mechanic.  This is why good programmers are worth 80K+
per year (if they are willing to work in the rigidly structured environment
that entails; some intellectual freedom is worth at least 20K a year).

> The attitude of most C experts is "*I* don't have any problem
> reading this code; anyone else who considers himself a Real C
> Programmer shouldn't, either."  This attitude is patronizing and
> wrong.  To borrow a previous argument, "We have to code in the
> real world, not the world of make-believe."  There are plenty of
> people out there who are still learning C, and since C has become
> as popular as it has and C programmers are in demand, many of
> them are working for real companies writing (and maintaining)
> real programs.

A beginning C programmer (say < 3 years if he isn't the type of person who
stays up 3 days straight coding) can not expect to understand, let alone
maintain, 65,000 lines of code, perhaps not even 8,000.  To expect him or
her to understand the UNIX kernel by writing it pretty is ignorance.  To
degrade your code (don't kid yourself; that's what it is) so that such a
programmer (actually, at that point, they are "a person who can program")
can understand it is marketing and technological suicide.  It is the
equivalent of redoing IQ tests so that 100 is average again.  Saying
someone is more intelligent doesn't make them so.  Writing code so that
someone at a lower knowledge (notice I did NOT say educational) level
can understand it does not make the reader a better programmer.

> A few painless concessions (like multiplying
> instead of shifting,

Thhhhhtp!  Painless my arse.  Look at the assembly with optimization
turned off.  An old compiler is like a new compiler without the optimization
turned on.

> (I maintain that every unnecessary
> translation from an encoding back to an intended meaning, no
> matter how intuitive to the experienced programmer, incrementally
> increases comprehension time, effort required, and probability of
> error.)

There is some truth to this, but the key word is "unnecessary".  It is
also unnecessary for a computer programmer, whose first abstract concept
should have been bits.  If the person is a C programmer, the second an third
concepts should have been Hexadecimal and Octal.  Assuming that these
operations haven't become automatic after experience is silly.  It is equally
silly to think of a programmer doing bit operations with multiplies instead of
shifts!

> If you leave the explicit n*n everywhere, this
> error is impossible.

So is speed.  You have any idea how long a multiply takes on most architectures?
I'll take the risk of having it blown out.  If it gets blown out, I'll do 
another thing programmers should know how to do to deserve the name -- debug.

> >Use register variables. And take a litle pain to get them right.  A
> >technique that works enough of the time is this:...

Yes, register variables are a good idea, even if you have a fasciesticly
optimizing compiler.

> I'm sorry, I don't have spare time or spare brain cells for
> following or utilizing a technique like this.

Not to be rude, but it would do you good to deallocate some of your non-spare
brain cells and realloc them to utilizing such things as register varaiables.

> When I was a beginning C programmer, I decided not to use
> register variables at all in the first pass of coding, because I
> figured that, when I eventually got the program working, somebody
> was going to say that it wasn't fast enough, and if I had already
> blown all of my ammunition, how was I going to speed it up?

This is at odds with your previous statement (which I agree with) of "code it
right the first time".  A properly written program can not be sped up without
sacrificing an unacceptable degree of portability.

> Lately I throw in "register" all the time, but in some ways I
> respect my original attitude more.  The point is, if it's the
> sort of program that people notice how fast it is, they would
> have complained even if I had used register variables since day
> one.

Bull pucky.

> Here's another religious debate: the structured programming one.
> Nevertheless, good modularity is the only way to keep a large
> program maintainable.

A correct statement.

> Of particular pertinence to the theme of
> this article is that efficiency-critical modules, if properly
> isolated, can be implemented quickly and (possibly) inefficiently
> first, while getting the program to work correctly at all, and
> then transparently replaced later with a more efficient version,
> if and only if it is found that the quick, inefficient
> implementation is really too inefficient after all.

Another way of looking at this is to rephrase it:

"will my slacking off be discovered?  If so, can I spend the time I
 should have in the first place to fix the problems I generated in
 slacking off?"

This is slipshod and a very bad attitude.

> If people have a bad impression of highly modular code, it is
> because they (or, more likely, their predecessors) have used what
> I call a "Ginsu knife" approach to modular decomposition, whereas
> the correct instrument to use is a scalpel.  If you went to a
> lecture once on modularity but the only guideline you remember is
> "50 source lines," you're going to use split -50 on your
> monolithic source files, and not garner any of the benefits of
> good modularity.  If, on the other hand, you learn how to
> modularize well, you just won't believe how easy your life (with
> respect to software development and maintenance, anyway) will
> become.

Lo!  Another correct statement!

> There are a few applications (and, some believe, a few
> architectures) for which function call overhead is significant,
> but they are infrequent, and in general there should be
> absolutely no stigma attached to a function call.  It's usually
> easy to back a function call out later, if you have to.

Look at the clock-tick time on a proc call and push/pops on the 8086.

> >:     o   Avoid bit fields, most especially signed ones.
> >Try: don't use them at all, they tend to be nonportable.
> 
> This is a side question: what's so unportable about bitfields?
> Sure, the layout (left to right or right to left) isn't defined,
> but that's only a problem if you're trying to conform to external
> layouts, which is a "problem" with structures in general.  (The
> solution is neither "don't use bitfields" nor "don't use
> structures" but "don't try to portably conform to external
> layouts.")  The ordering could also be a problem if the code
> internally depended on it in some way, but this is no more or
> less a problem than programs which depend on byte ordering
> within words.

Why use bitfields at all if they are simply internal representations?
The insertion/extraction overhead far outweighs any memory advantages.
If not, you should re-examine your basic data structures more closely.

> Are there substantial numbers of real (not toy) C compilers that
> either don't implement bitfields, or that don't implement them
> correctly?  ("Correctly" as defined by K&R and ANSI, not by some
> program that is trying to use them nonportably.)

Yes.

> >:     o   Use realloc instead of malloc/free pairs. Use calloc instead of
> >:         malloc followed by zeroing each member.
> >Efficient memory allocation is *much* more difficult than this, what
> >you really need to do is to cut down on the number of calls to malloc
> >and free. Of course, that usually means writing some kind of
> >allocation stuff yourself, never an easy task.

Agreed.  I would add, however, that if you don't really need a calloc, using
a malloc with a multiply is more efficient in that it does not have to
generate a loop (or some other code) to clear the newly allocated area.

> Please don't avoid malloc; the alternative is generally
> fixed-size arrays and "lines longer than 512 characters are
> silently truncated."  Please don't roll your own allocation
> routines, again unless you have to; this is the kind of low-level
> dirty work, hard enough to do well, that you should let the lower
> levels (i.e. the standard implementations of malloc) give it
> their best shot.

I can name 5 "real compilers" off the top of my head whose in-line expansion
of memory allocation or whose standard library routines are broken.

> >: Micro-efficiency gets regularly denounced. Here are some counterarguments:
> >: o       If your commercial product is slower than the competition, you
> >:         won't get a chance to take advantage of the maintainability/
> >:         portability advantages because you'll be out of business.
> >Or if it is larger. This discussion has focused on making programs
> >faster, but making them smaller is also relevant.
> 
> I agree, and find code size far more frequently relevant in
> practice (among other things, because of the aforementioned
> pdp11).

Not to mention the Altos/Harris/SCO/MicroSoft medium-is-my-largest-model
8086 and 80186 Xenix's (Xenixi?).

> I might also point out that the marketplace generally rewards the
> product that comes out first, so if you can compress your
> development schedule by using clean, straightforward design and
> eschewing time-consuming and bug-prone efficiency tweaking,
> you may come out ahead (and sew up your market share and your
> pocketbook by releasing a speeded up version 2 six months later).

I point at Lotus and snicke at the above paragraph.

[discussion of memory model limitations]

> While these considerations are real, they should be viewed as
> unfortunate and temporary limitations which will be resolved,
> hopefully soon enough, at the root level, and not something which
> we resign ourselves to working around forever, and which we get
> so used to working around that the workarounds become part of the
> language, persisting well beyond their need.  These are what Fred
> Brooks calls "accidental problems," and every programmer minute
> and line of code spend catering to them is not being spent on
> real problems.

Yes, but there are too many machines with these problems (unless you know
something I don't about UCB coming out with new UNIX 7 and UNIX 7 compiler
revisions :-).

> I'll third the motion, but without the tweaks.  My proudest code
> is that which is not only small and fast and elegant and portable
> and modular and extensible but also patently obvious in function
> to the casual observer

A casual observer has no place in a programming shop.

> But it does mean that if you unilaterally tell a reasonably green
> programmer to use pointers instead of arrays, he'll spend months
> having lots of frustrating problems with pointers, and likely end
> up hating them and the language in general.

Or he or she will learn how to use them.  I would rather have a programmer
unwilling to learn something they don't know (although they'd damn well better
know pointers; they're basic!) quit or be fired.


Sorry to seem so hard on Steve, but the ideology embodied in his statements
and statments that "the optimizer will take care of it" is so antithetical
to programmerness that I had to say something.


| Terry Lambert           UUCP: ...{ decvax, uunet } ...utah-cs!century!terry |
| @ Century Software        OR: ...utah-cs!uplherc!sp7040!obie!wsccs!terry    |
| SLC, Utah                                                                   |
|                   These opinions are not my companies, but if you find them |
|                   useful, send a $20.00 donation to Brisbane Australia...   |
|                   'I have an eight user poetic liscence' - me               |

bill@twwells.uucp (T. William Wells) (11/06/88)

Grrrrrrr........

In article <2730@hound.UUCP> rkl1@hound.UUCP (K.LAUX) writes:
: | : >... dividing positive numbers by powers of two should be done with a shift.
:
:       I'm suprized that noone has questioned the validity of shifting
: instead of dividing by (powers of) 2.

No one has questioned it because, given the rider that *everyone* has
attached (the thing being divided being positive), it *is* valid.

:       What about the difference between Logical and Arithmetic shifts?

There is none, for positive numbers.

: If you want to divide, then divide!  A lot of compilers are smart enough
: to implement the divide as a shift, but only where appropriate.

And many are not. Worse, most frequently, the compiler can't
determine whether the number really is positive. In fact, only the
most advanced compilers are capable of this.

The exception to this is when the number being divided is declared
unsigned.  So, the options are a cast which may or may not cause the
optimizer to do what one wants, or a shift which will.

:       I *did* notice the condition of dividend being positive,

But you obviously decided to ignore the condition. Or you failed to
consider that it is a critical condition.

:                                                                but now
: you have to *guarantee* that it will always be positive.

So? If I can't determine correctly that such is true, I have much
worse problems than code which doesn't meet your approval.

:       As for the 'compiler being broken' if it doesn't do the same thing
: for a shift and dividing, this is not true.  In one case, you only told
: it to shift, the other to do a divide:  they made be *equivalent*, but
: they certainly are not *equal*.

Go back to school. If they aren't equal, then they aren't equivalent.
If they are equivalent then the must be equal.  Under appropriate
restrictions, a>>b and a/(2**b) are equal, thus equivalent.

:       So, please, if you *functionally* require a divide by (powers of) 2,
: code it as a divide and let the compiler *implement* it (maybe optimization
: needs to be turned on).

So please, get your head straight, and learn to think, before
intruding your ignorance in the debates of your betters.

---
Bill
{uunet|novavax}!proxftl!twwells!bill

vfm6066@dsacg3.UUCP (John A. Ebersold) (11/08/88)

I have been following this for some time.

My two cents...

Balance all coding "tricks" against the need to produce readable and thus
maintainable code.  

Get the code working properly first, make sure it can be read and understood
by programmers less talented than yourself.

Comment liberally, (the l-word and oh nooo the c-word), however do not
re-state the obvious, e.g,

#define EOF 0

/*
** Read until end of file  (silly comment)
*/
while (fread != EOF)

(Don't laugh, I've seen it done).

Misleading comments are worse than no comments at all.  If your algorithm
requires five times as many lines of as the code - so be it.  You may be
immersed in it now, but try and remember what is going on six months from
now - pity on the poor maintainer.

The most important comments are those that give the BIG PICTURE, followed by
those that show how the parts fit into the whole.

Lastly, if it is too slow, profile it and fix the slow parts in a readable
manner.   Humbley (is that a word?).

knudsen@ihlpl.ATT.COM (Knudsen) (11/08/88)

In article <1630@scolex>, seanf@sco.COM (Sean Fagan) writes:
> If I were to look at that, I would say that the coder used a short because
> he/she wanted to document the fact that the value was not expected to
> overflow a short. (is anybody following me?  if so, could you explain it to
> me? 8-)  Here's another shot:)  I.e., "I don't expect the value to be larger
> than 32k-1 [on most 32-bit machines, for example]."  It's what I would do.

What I do for variables whose size I know is limited,
but where I want to retain the efficiency of "int", is:

typedef int sexy;

and then declare such vars as sexy.  This reminds me that these
would fit perfectly well in a byte, but should be computed as int
to avoid the oft-mentioned promotions to int.
It's especially helpful if/when I rewrite a function in assembler,
where the efficiency problem mostly goes away.

If I got terrible strapped for data memory, I could change the
typedef so that sexy was really char.  This would expand and slow
down the code, as others have noted.

Some of you may have guessed I write for the 6809, whose SEX
(sign extend) instruction is the mainstay of promoting
bytes (chars) to 16-bit ints.  Of course most of you are worried
about 16-bit shorts and 32-bit ints, but the principle is the same.
You may call your version "shorty" ... :-)

My 6809 C compiler *is* smart enough to test chars as just one byte
(no SEXing to int), so I typedef'ed "bool" to "char."

BTW, a year ago there was a discussion about typedef'ing all sorts
of custom types, and NEVER using the straight char/short/int/long
types in your own code.  I don't go this far, but I do have
"byte" and "ubyte" and use "char" only for ASCII items;
plus I have some custom "int" types like "index" and "time."
But I use "int" for plain old loop counters and such.
-- 
Mike Knudsen  Bell Labs(AT&T)   att!ihlpl!knudsen
"Lawyers are like nuclear bombs and PClones.  Nobody likes them,
but the other guy's got one, so I better get one too."

krohn@u1100a.UUCP (Eric Krohn) (11/09/88)

In article <143@twwells.uucp> bill@twwells.UUCP (T. William Wells) writes:
] 
] Formulated as a general principle: if an operand in a multiply or the
] divisor in a divide or mod (with the other operand being known to be a
] positive number) is one which in the problem being solved is
] naturally a power of two, it is reasonable to use bitwise operators to
] implement the operation, provided that a #define is used to define
] the number and (when needed) its log base 2.

Instead of #defining the log in terms of the number,
I prefer to #define the number in terms of its log base 2, as in

#define	LOGSIZE	7
#define	SIZE	(1 << (LOGSIZE))

to insure that SIZE really is a power of 2 when I expect it to be.
For the safety conscious, you can add

#if	LOGSIZE <= 0
#include	"LOGSIZE must be non-negative"
#endif
#if	LOGSIZE >= BITSPERWORD
#include	"LOGSIZE is too large"
#endif
-- 
--
Eric J. Krohn
krohn@ctt.ctt.bellcore.com  or  {bcr,bellcore}!u1100a!krohn
Bell Communications Research,	444 Hoes Ln,    Piscataway, NJ 08854

dharvey@wsccs.UUCP (David Harvey) (11/09/88)

The recent postings on the net on this topic has prompted me to respond
about a teacher at our college (not necessarily reflecting the opinion
of all faculty members) who would fail me for the following code:

if( ! something) {
	++j;
	.
	.
	.
}

but would pass me with flying colors for the following:

if ( something != 0)
	{
		j = j + 1;
		.
		.
		.
	}

As a matter of fact, he would under no circumstances allow me to
use the '++', '--', '+=', '-=', '/=', '*=', or '%=' operators.
I would LITERALLY be failed if it were there.  And yes, he feels
that Pascal is much better than C, and Modula2 better yet.  In
other words, C does have philosophy of coding that emanates a
flavor of efficiency.  Use it or lose it.  By the way, another
teacher here loves ANSI C, and every 'enhancement' he mentions
smacks me of being Pascalish in orientation.  For example, the
declaration of variables within the function parentheses will
provide the 'protection' that Pascal lovers want only if the
compiler can compile both the caller and callee at the same time.
Or the linker must be more sophisticated, using information that
is generated by the compiler.  How will it be handled folks?
Either way we lose.  If the compiler does it, there goes our
modular strategy of putting related functions in seperate files
from the functions that use them.  But then we could always
#include C code files, which is exactly what one of the afore-
mentioned teachers required of beginning C students!  But what
if I want to package a set of functions for users of equipment
I manufacture to connect to the computer and I don't want them
to have the source?  Then the linker will have to do it.  Will
that cost more for the compiler and linker?  You bet your boots
it will!  My point of view?  If you want to program in Pascal,
Modula2, Ada, et al, by all means do so.  But don't cram your
views that make a machine run slower down my throat!  If I want
to do that I will use Lisp or Prolog where I get something back
for what I lost!

dharvey@wsccs

chris@mimsy.UUCP (Chris Torek) (11/10/88)

In article <23459@amdcad.AMD.COM> tim@crackle.amd.com (Tim Olson) writes:
>A good compiler can also recognize a signed integer divided by a power
>of 2, and use the following trick:
[copy and add sign bit]

It is worth noting that Sun's PCC-based compiler does something similar:

	f() {
		register i, j;
		...
		i = j / 2;
		...
		i = (unsigned)j / 2;
		...
	}

compiles (using the SunOS 3.5 compiler running under SunOS 3.2) to

	movl	d6,d0
	tstl	d0
	jge	L2000000
	addql	#1,d0
L2000000:
	asrl	#1,d0
	movl	d0,d7

(clearly not the best code in the world) and

	movl	d6,d0
	lsrl	#0x1,d0
	movl	d0,d7

(why some constants in hex and others decimal we may never know :-) ).
The optimiser transforms the latter to the more appropriate

	movl	d6,d7
	lsrl	#1,d7

(yes, the 0x disappears).
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

ok@quintus.uucp (Richard A. O'Keefe) (11/11/88)

In article <764@wsccs.UUCP> terry@wsccs.UUCP (Every system needs one) writes:
>if statements should look like:
>
>	if( expression operator expression)
>rather than
>	if (expressionoperatorexpression)
>
>which can break old compilers with either the space after the if or

WHAT?  I've used C under UNIX since version 6+, and various VM/CMS,
8086, and VMS C compilers, and I've _never_ run into one with THAT
bug!  The rule that I use is that "things which are similar should
look similar, things which are different should look different";
if statements are not function calls, so they shouldn't look like
them.  Say "squodge 'if' up to the '(' and confuse the h--- out of
readers because I _like_ it that way", but don't say that compilers
were ever allowed to _demand_ it.

guy@auspex.UUCP (Guy Harris) (11/12/88)

>Either way we lose.

"Either way" is generally used when there are two ways.  There is a
third way, which, although it may not catch type mismatches *all* the
time, will probably catch a hell of a lot of them:

	put the prototypes into an #include file, and make sure the
	module that defines a function includes the #include file that
	declares that function.

The compiler will complain if the prototype in the #include file does
not match the prototype on the function itself.

BTW, putting function declarations in an #include file is a Very Good
Idea even if you don't have prototypes; without prototypes, you may not
be able to declare the types of the arguments to the function, but you
can declare the type of the function's return value, which is
important.... 

>But don't cram your views that make a machine run slower down my
>throat!

I have yet to see any indication that adoption of stronger-type-checking
notions in the dpANS will "make a machine run slower" by any significant
amount.

In fact, if prototypes had been there since Day 1 and had been the
*only* way of declaring functions (this may perhaps have made the
language too big for the PDP-11 to compile, I don't know - I'm not
saying that this would necessarily have been the best thing), and if
"varargs" or "stdarg" had been the only permissible way of doing
variable-length-argument-list functions, calling sequences where the
callee, rather than the caller, could safely have been used (since the
compiler could feel reasonably confident that if a function expects N
arguments of particular types, it will be passed just such a list of
arguments), and it has at least been asserted that on some machines,
such calling sequences are faster (e.g., the debates over the "C" and
"Pascal" calling sequences on 80*86 machines).

"Strong typing is for weak minds" is for weak minds.

nsw@cord.UUCP (Neil Weinstock) (11/12/88)

In article <771@wsccs.UUCP> dharvey@wsccs.UUCP writes:
>The recent postings on the net on this topic has prompted me to respond
>about a teacher at our college (not necessarily reflecting the opinion
>of all faculty members) who would fail me for the following code:
>
>if( ! something) {
>	++j;
>	...
>}
>
>but would pass me with flying colors for the following:
>
>if ( something != 0)
>	{
>		j = j + 1;
>		...
>	}
>
>As a matter of fact, he would under no circumstances allow me to
>use the '++', '--', '+=', '-=', '/=', '*=', or '%=' operators.

I have heard this issue thrashed out before.  I feel very strongly that
the operators you list can *enhance* readability when used appropriately, and 
therefore I would assert that the teacher you refer to is extremely misguided 
(boy, I'm being nice).

In general, when doing an increment operation or some such, I find it
convenient to think of it as an operation on a variable, not as an assignment
(fine shades of meaning, to be sure).  C's various wonderful assignment
operators reflect this naturally, and it is one of the many reasons I like C.

This issue becomes important when you get big lvalue expressions:

	array[row*2+offset][col+loopvar] += 8;

becomes

	array[row*2+offset][col+loopvar] = array[row*2+offset][col+loopvar] + 8;

This is a contrived example, but I frequently encounter such stuff.  I would 
assert that the first is infinitely more readable than the second.

So, while the operators in question are certainly abusable, they have their
place.  It is difficult to imagine using C voluntarily without them, and I
shudder to think of what some code would look like without them.

.- -- .. --. .- .-. ..- .-.. . ... .- -- .. --. .- .-. ..- .-.. . ...
|  Neil Weinstock | att!cord!nsw     | "I think my cerebellum just  |
|  AT&T Bell Labs | nsw@cord.att.com |      fused." - Calvin        |
.- -- .. --. .- .-. ..- .-.. . ... .- -- .. --. .- .-. ..- .-.. . ...

gwyn@smoke.BRL.MIL (Doug Gwyn ) (11/12/88)

In article <771@wsccs.UUCP> dharvey@wsccs.UUCP (David Harvey) writes:
>... who would fail me for the following code:
>if( ! something) {
>	++j;
>but would pass me with flying colors for the following:
>if ( something != 0)
>	{
>		j = j + 1;

If "something" is conceptually Boolean, then even your instructor
should think !something is preferable to something==0 (note the bug
fix) since his favorite languages have explicit Boolean NOT operators.
On the other hand, if "something" is conceptually arithmetic, then
something==0 is stylistically preferable to !something.

++ is obviously thought of as a unary increment operator, which those
other langauges don't have (which is probably why your instructor is
not comfortable with the idea).

>declaration of variables within the function parentheses will
>provide the 'protection' that Pascal lovers want only if the
>compiler can compile both the caller and callee at the same time.
>Or the linker must be more sophisticated, using information that
>is generated by the compiler.  How will it be handled folks?

I see you don't understand how function prototypes work.
(Perhaps this is due to your instructors also?)
The compiler can (indeed, must) check function arguments and
parameters against the corresponding prototype when it is in
scope.  Typically this occurs when a prototype declaration is
supplied in an "interface" header file that is #included near
the beginning of each source file that refers to things defined
or declared by that header.

>But then we could always #include C code files, which is exactly
>what one of the afore-mentioned teachers required of beginning C
>students!

Obviously he has a Pascal mind-set.

One of C's important features is the support for separate
compilation.  ANSI C's function prototypes in no way weaken
this; in fact they better support it.  No linker support is
required beyond that required by "K&R C".

-----

My advice to you as a student is to learn proper C usage from
books by people like Brian Kernighan and Tom Plum.  (There are
other good authors of C books too, as well as scores of bad
ones.  I just picked a couple of the best.)  Give your
instructors whatever silly crap they demand and don't waste
time worrying about it or trying to educate your instructors.
School serves two main practical purposes: it gets you a scrap
of sheepskin that helps you get a desirable job, and it
potentially helps you learn how to learn under your own steam.
The actual data content of your courses won't usually do you
much good later in life, but general methods of gathering and
evaluating information remain valid "forever".

henry@utzoo.uucp (Henry Spencer) (11/13/88)

In article <771@wsccs.UUCP> dharvey@wsccs.UUCP (David Harvey) writes:
>... But what
>if I want to package a set of functions for users of equipment
>I manufacture to connect to the computer and I don't want them
>to have the source?  Then the linker will have to do it.  ...

Nonsense.  Supply them with an include file.  It doesn't contain
any more information than your documentation will have to have anyway.
-- 
Sendmail is a bug,             |     Henry Spencer at U of Toronto Zoology
not a feature.                 | uunet!attcan!utzoo!henry henry@zoo.toronto.edu

mouse@mcgill-vision.UUCP (der Mouse) (11/14/88)

[ > from <137@twwells.uucp>, by bill@twwells.uucp (T. William Wells) ]
[ >> from <7700@bloom-beacon.MIT.EDU>, by scs@adam.pika.mit.edu (Steve Summit) ]
[ >>> from <119@twwells.uucp>, by bill@twwells.uucp (T. William Wells) ]

>>> Avoid excess function calls.  A lot of people have been brainwashed
>>> by some modularity gurus into believing that every identifiable
>>> "whole" should have its own function.
>> Here's another religious debate: the structured programming one.
> I think you missed the point.  Structured programming is essential to
> good procedural programming.

The truth of this depends on what you mean by "structured programming".
An unfortunately large segment seems to believe that structured
programming means sticking strictly to certain rules, such as "never
use a goto, ever" or "no function body larger than a page, ever"
(though they never say whether terminal page or paper page, curious).
And, as I am sure you are aware, blind adherence to rules cannot, in
itself, produce good programs, regardless of the rules.  Good
programmers write good programs without needing rules; bad programmers
can't write good programs even with rules.  I suspect their reason for
existence is that good programmers' output tends to follow them (not
always, though!), so in a wonderful inversion of cause and effect, some
people deduce that following the rules will make for good programs.
(Using such rules as guidelines may also help nudge in-between
programmers the right way.)

>> There are a few applications (and, some believe, a few
>> architectures) for which function call overhead is significant,

On a VAX, almost all compilers (certainly all of which I am aware,
except possibly for BLISS) use the CALLS/CALLG function call mechanism.
This mechanism is so horribly inefficient it arguably shouldn't have
existed in the first place.  The VAX is certainly not more than one
architecture, but it's a rather common one.  (As Steve implies, this
doesn't matter for the vast majority of programs.  I am particularly
aware of it because I have been involved in an effort to build a robot
control system, which involves guaranteeing a 28-millisecond cycle time
for certain parts of the code, and a CALLS - to pick just one example -
is a nontrivial fraction of that.)

>> Please don't avoid malloc; the alternative is generally fixed-size
>> arrays and "lines longer than 512 characters are silently
>> truncated."

I have been guilty of going the fixed-size route on occasion, and
doubtless will be in the future.  Why?  Because allocating requires
that I know the size ahead of time.  If there were some way to inquire
of "the system" how much data remains to be read before the next
newline....but there isn't, and probably never will be.  For most
programs, the user-interface flaw implied by fgets() is less painful
than the time it would eat up for me to go the general route.

>>> Unless, of course, the programmer remembered to COMMENT.
>> If the code reads
>>       a &= 3;         /* really a %= 4 */
>> or
>>       a &= 3;         /* really a %= HASHSIZE */
>> and I do a global replace of 4, or re#define HASHSIZE, the comment
>> may not help.
> Yes, but writing an explicit constant is bad to start off with.  It
> should be:
> #define HASHSIZE 4              /* A power of two, or else! */
> 	a &= HASHSIZE - 1;

How about

#if HASHSIZE & (HASHSIZE-1)
	a %= HASHSIZE;
#else
	a &= HASHSIZE - 1;
#endif

Of course, I daresay there aren't that may hashing algorithms that work
well for both power-of-two table sizes *and* non-power-of-two sizes.
But the choice of algorithm is not the point here.

					der Mouse

			old: mcgill-vision!mouse
			new: mouse@larry.mcrcim.mcgill.edu

wald-david@CS.YALE.EDU (david wald) (11/16/88)

In article <771@wsccs.UUCP> dharvey@wsccs.UUCP (David Harvey) writes:
>The recent postings on the net on this topic has prompted me to respond
>about a teacher at our college (not necessarily reflecting the opinion
>of all faculty members) who would fail me for the following code:
>
>if( ! something) {
>       ++j;
>       .
>       .
>       .
>}
>
>but would pass me with flying colors for the following:
>
>if ( something != 0)
>       {
>               j = j + 1;
>               .
>               .
>               .
>       }
>
>As a matter of fact, he would under no circumstances allow me to
>use the '++', '--', '+=', '-=', '/=', '*=', or '%=' operators.
>I would LITERALLY be failed if it were there.

Just out of curiosity:

Why the hell was he teaching in C?


============================================================================
David Wald                                              wald-david@yale.UUCP
						       waldave@yalevm.bitnet
============================================================================

bill@twwells.uucp (T. William Wells) (11/16/88)

In article <1349@mcgill-vision.UUCP> mouse@mcgill-vision.UUCP (der Mouse) writes:
: The truth of this depends on what you mean by "structured programming".

Try this version of structured programming: the physical structure of
the program should mirror the logical structure of the program.

: An unfortunately large segment seems to believe that structured
: programming means sticking strictly to certain rules, such as "never
: use a goto, ever" or "no function body larger than a page, ever"
: (though they never say whether terminal page or paper page, curious).
: And, as I am sure you are aware, blind adherence to rules cannot, in
: itself, produce good programs, regardless of the rules.  Good
: programmers write good programs without needing rules; bad programmers
: can't write good programs even with rules.  I suspect their reason for
: existence is that good programmers' output tends to follow them (not
: always, though!), so in a wonderful inversion of cause and effect, some
: people deduce that following the rules will make for good programs.
: (Using such rules as guidelines may also help nudge in-between
: programmers the right way.)

All of which I heartily agree with.

: I have been guilty of going the fixed-size route on occasion, and
: doubtless will be in the future.  Why?  Because allocating requires
: that I know the size ahead of time.  If there were some way to inquire
: of "the system" how much data remains to be read before the next
: newline....but there isn't, and probably never will be.  For most
: programs, the user-interface flaw implied by fgets() is less painful
: than the time it would eat up for me to go the general route.

Write a function to handle it once and for all.  Then always use the
function. This is what I'm going to start doing.

: #if HASHSIZE & (HASHSIZE-1)
:       a %= HASHSIZE;
: #else
:       a &= HASHSIZE - 1;
: #endif
:
: Of course, I daresay there aren't that may hashing algorithms that work
: well for both power-of-two table sizes *and* non-power-of-two sizes.
: But the choice of algorithm is not the point here.

My own feeling is that if HASHSIZE isn't known to be a power of two,
one should live with the %. The alternative is to turn one's code
into a morass of #if's. Ugh.

---
Bill
{uunet|novavax}!proxftl!twwells!bill

scs@athena.mit.edu (Steve Summit) (11/19/88)

Not surprisingly, I got ripped to shreds by Bill Wells and Terry
Lambert (among others) for having dared to suggest that good
style might be more important than raw efficiency and
microoptimization.  (Don't worry; this won't be a line-by-line
retribution; I've got better things to do.)

The reason I opened my earlier posting by casting "efficiency vs.
style" as a religious war was to suggest that I did not expect to
convince the efficiency aficionadoes that efficiency is not as
important as they think it is, any more than I expect them to
convince me that it is.  ("Efficiency aficionado" is not meant as
a pejorative term; some people simply have differing opinions
on the relative importance of things like efficiency and style.
The fact that these opinions are polarized and strongly held
makes for a fine religions war.)

There is an abominable quantity of horrible code in existence
today, and egregious amounts of time are wasted trying to fix
neverending bugs, trying to add seemingly reasonable features
which the "architecture" inexplicably prohibits, and finally
abandoning the whole mess and rewriting it from scratch.  I will
be vehemently disagreed with, but I submit that most of this
unmaintainable code arises out of misplaced concerns for
"efficiency."  It is true that there are a lot of miseducated
programmers out there, but their miseducation often derives from
teachers and textbook authors who constantly harp on efficiency
without teaching moderation, and from the badly-written code by
which they are surrounded and which they (the students)
inevitably emulate and go on to perpetuate.

I am not, repeat not, saying that efficiency is not important.  I
am not advocating (in this article, anyway) writing deliberately
inefficient code just to make it clearer.  I am saying that style
may be more important than efficiency, particularly since not all
programmers are able to "have their cake and eat it too."
Certainly we need to educate people in this direction; certainly
we need to teach them techniques for writing code that is right
the first time (where "right" is clean, clear, and not
inefficient).  When assigning priorities, though, I would rather
have people learn clarity and good style first, and efficiency as
the need arises.  (Among other things, the marketplace
imperatives of which we are endlessly reminded will ensure that
efficiency will not be neglected; while there are, sadly, not as
many incentives for writing clearly.)  The industry is not
plagued by slow programs (compilers being tuned for the
benchmarks to the contrary notwithstanding).  The industry is
plagued by software that is over schedule, over budget, buggy,
and unmaintainable.

Please: those who keep coming down like a ton of bricks (and no,
I don't take it that way; this is a religious war--I know I'm
right :-)) when it is even suggested that efficiency might not be
all-important: I am not saying that you are wrong, merely that
I'd like to shift the emphasis.  (I would have more sympathy,
though, with articles which claim they agree that style is
important while continuing to trot out obfuscated C contest
candidates, if they didn't doggedly insist that x<<1 is still
better than x*2, when it has been adequately shown that the
former contributes nothing to efficiency and can only detract
from readability.  Quit saying that not all compilers can do
these optimizations--to quite Kernighan and Plauger, "Those that
will not must certainly provide worse inefficiencies to worry
about.")

I forgot to recommend The Elements of Programming Style
(Kernighan and Plauger) in my previous article.  If you haven't
read it already, do so; and read it again if you have.  It's got
more solid, practical advice on good style on each page than
you'll see on the net in a year, and it's got a whole chapter on
efficiency, too (relegated to the back, where it belongs).  Don't
let the Fortran and PL/I throw you--with a little thought all of
the issues being discussed can be seen still to be supremely
important today.

                                            Steve Summit
                                            scs@adam.pika.mit.edu

rkl1@hound.UUCP (K.LAUX) (11/22/88)

	Steve very eloquently states his observations/conclusions about the
state of the Software Industry.

	I recently read an article pointing out that there have been
tremendous advances made in hardware, but relatively few in software, and
that predicts a woeful shortage of Software Engineers (or whatever you
would like to call them) for the future.

	Case in point:  It is now possible to design IC's from scratch, and
have them in a production run, in about 3 months, from a single workstation.

	So, how many programs are there now that take advantage of all (or
even a lot) of the features of OS/2?  Or VGA.  Or 80386?

	I sure hope that the Software Industry has a break though like the
Hardware Industry soon.  At least I can be sure of steady employment :-) if not.

--rkl

gwyn@smoke.BRL.MIL (Doug Gwyn ) (11/22/88)

In article <2766@hound.UUCP> rkl1@hound.UUCP (K.LAUX) writes:
>	So, how many programs are there now that take advantage of all (or
>even a lot) of the features of OS/2?  Or VGA.  Or 80386?

That sure is strange criteria for software!

john@frog.UUCP (John Woods) (11/22/88)

In article <8059@bloom-beacon.MIT.EDU>, scs@athena.mit.edu (Steve Summit) writes:
R> I forgot to recommend The Elements of Programming Style
e> (Kernighan and Plauger) in my previous article.  If you haven't
a> read it already, do so; and read it again if you have.  It's got
d> more solid, practical advice on good style on each page than
 > you'll see on the net in a year, and it's got a whole chapter on
i> efficiency, too (relegated to the back, where it belongs).  Don't
t> let the Fortran and PL/I throw you--with a little thought all of
!> the issues being discussed can be seen still to be supremely
 > important today.

I would like to second this recommendation.  It is also the source of my
favorite quote, which happens to be on this very topic:  "Presumably it was
essential to get the wrong answer as fast as possible."
 
-- 
John Woods, Charles River Data Systems, Framingham MA, (617) 626-1101
...!decvax!frog!john, john@frog.UUCP, ...!mit-eddie!jfw, jfw@eddie.mit.edu

Science does not remove the TERROR of the Gods!

anw@nott-cs.UUCP (11/23/88)

In article <437@auspex.UUCP> guy@auspex.UUCP (Guy Harris) writes:
>
>In fact, if prototypes had been there since Day 1 and had been the
>*only* way of declaring functions (this may perhaps have made the
>language too big for the PDP-11 to compile, I don't know - I'm not
	  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	Naw!  Let's see [PDP 11/44, V7]:

	$ ls -l /lib/c?
	-rwxrwxr-x   1 bin	system	   23924 May  7  1979 /lib/c0
	-rwxrwxr-x   1 bin	system	   35282 May 16  1979 /lib/c1
	-rwxrwxr-x   1 bin	system	   11632 Jul 11  1980 /lib/c2
	$ size /lib/c?
	/lib/c0: 20736+3172+11552 = 35460b = 0105204b
	/lib/c1: 30592+4674+1206 = 36472b = 0107170b
	/lib/c2: 10176+1440+2430 = 14046b = 033336b

	Plenty of room there for expansion, even with a 64K limit!

>saying that this would necessarily have been the best thing), and if
>"varargs" or "stdarg" had been the only permissible way of doing
>variable-length-argument-list functions, calling sequences where the
>callee, rather than the caller, could safely have been used (since the
>compiler could feel reasonably confident that if a function expects N
>arguments of particular types, it will be passed just such a list of
>arguments), and it has at least been asserted that on some machines,
>such calling sequences are faster (e.g., the debates over the "C" and
>"Pascal" calling sequences on 80*86 machines).

	Of course, the real solution to the "varargs" problem, not possible
in C because of the dead hand of history, is to use extra brackets, so that
every function has a fixed number of arguments:

		printf ("%d %c %s\n", (i, c, "hello world"));
			^...arg1...^, ^.......arg2........^

	Now, what language did I see that nifty idea in? [:-)]

-- 
Andy Walker, Maths Dept., Nott'm Univ., UK.
anw@maths.nott.ac.uk

guy@auspex.UUCP (Guy Harris) (11/25/88)

>	Naw!  Let's see [PDP 11/44, V7]:

It may well be true that it can be done - I certainly won't say it's
impossible, I was just considering the possibility that it wasn't in the
original C language because either 1) it would have made the compiler
too big or 2) DMR thought it would.

At this point, I'd prefer to hear Dennis say why, when C was first
created, the types of the arguments to a function was not considered
part of the type of the function, or at least an answer from somebody
working with him at the time.  Most other answers would probably be
purely speculative.

>	Of course, the real solution to the "varargs" problem, not possible
>in C because of the dead hand of history, is to use extra brackets, so that
>every function has a fixed number of arguments:
>
>		printf ("%d %c %s\n", (i, c, "hello world"));
>			^...arg1...^, ^.......arg2........^
>
>	Now, what language did I see that nifty idea in? [:-)]

OK, so what is the data type of

	(i, c, "hello world")

Just adding extra brackets wouldn't have been sufficient; it leaves
questions such as that one....

dik@cwi.nl (Dik T. Winter) (11/25/88)

 > >	Of course, the real solution to the "varargs" problem, not possible
 > >in C because of the dead hand of history, is to use extra brackets, so that
 > >every function has a fixed number of arguments:
 > >
 > >		printf ("%d %c %s\n", (i, c, "hello world"));
 > >			^...arg1...^, ^.......arg2........^
 > >
 > >	Now, what language did I see that nifty idea in? [:-)]

(For those who do not know, Algol 68)
 > 
 > OK, so what is the data type of
 > 
 > 	(i, c, "hello world")
 > 
 > Just adding extra brackets wouldn't have been sufficient; it leaves
 > questions such as that one....

It is an 'array display', i.e. the second parameter is an array whose
components are a union of a lot of things.  The extra brackets are
required as there is (in Algol 68) a difference between:
	print(a, b, c)
and
	print((a, b, c))
the first calls print with three parameters (which is illegal),
the second with only one.


-- 
dik t. winter, cwi, amsterdam, nederland
INTERNET   : dik@cwi.nl
BITNET/EARN: dik@mcvax

peter@ficc.uu.net (Peter da Silva) (11/28/88)

In article <2766@hound.UUCP>, rkl1@hound.UUCP (K.LAUX) writes:
> 	So, how many programs are there now that take advantage of all (or
> even a lot) of the features of OS/2?

Ummmm... isn't that a piece of software?

> Or VGA.  Or 80386?

UNIX? With X-windows?
-- 
Peter da Silva  `-_-'  Ferranti International Controls Corporation
"Have you hugged  U  your wolf today?"     uunet.uu.net!ficc!peter
Disclaimer: My typos are my own damn business.   peter@ficc.uu.net