Skip to main content

C TIPS(!) AND TRICKS(?) by Mark van den Boer

In this article I will explain some programming techniques  which
are  useful to C-programmers who like to code  for  speed.  These
techniques (some call 'em tricks) are applicable on every machine
although some are 68000 specific.
The  techniques will deal with individual functions and not  with
the  complete  program.  Naturally,  if  you  optimize  the  most
important functions the complete program will execute faster.
The  benefits  of  these  optimization  techniques  are   largely
dependent of the compiler you are using,  since compilers  differ
strongly in the way they optimize their code.

The techniques can be divided in a number of categories:

- General programming
These techniques can be used in every language.

When   testing  for  certain  conditions  always  test  for   the
condition that is most of the time true.  E.g. if a variable lies
in 60% of all cases between 0 and 10, in 30% of all cases between
10 and 20 and in the other 10% it has another value,  then  first
test  if  the variable lies between 0 and 10,  then test  if  the
variable  lies  between 10 and 20.  In this case there is  a  60%
chance you will only have to test once, which is of course faster
than testing multiple times.
When  testing for some particular value,  try to compare as  many
times  as  possible with zero,  since nearly each  machine  tests
faster for zero than for other values.  E.g. if (i >= 1)  could be
replaced by if (i > 0)  which is more efficient.
Finally,  I  must admit when you really want speed you've got  to
program in assembler.  No good high level language programmer can
make  a  faster program than a good  assembler  programmer.  E.g.
sprite  and scrolling routines are best programmed in  assembler.
It also must be mentioned that nearly every C-compiler allows the
programmer to call assembler routines.  Making assembler  written
functions  accessible as if they were C-functions is  a  facility
most  C  packages  support.  This  is  achieved  by  putting  the
assembler  written  function in a library  using  the  librarian.

However,  you should be aware of the function-calling conventions
used by your particular C-package.

- Arithmetic operations
Some integer arithmetic operations can be performed in more  than
one way.  Multiplication,  division and modulo are a good example
of such operations.  When performing one of these operations with
a power of 2 there is a faster way in C. Multiplication: var << 2
has the same result as var * 4  but is faster. Division: var >> 1  
has the same result as var / 2  but is faster.  Modulo:  var & 16
has  the same result as var % 16 but is faster.  As a  matter  of
fact, even var << 4 + var << 6  is faster than var * 80 .
Use as little operations on floating point variables as you  can,
since  these  operations  are extremely  time-consuming  if  your
computer hasn't got a mathematics coprocessor.  Work hard to keep
away  from floating points variables.  E.g.  if you have to  deal
with  percentages  you could still use  integer  variables  while
making it appear to the outer world as if you were using floating
points variables. This can be done by multiplying all percentages
with 100 and then do your calculations with these integers.  When
printing the results you can do it as follows: Suppose variable i

contains 1234 which is equal to 12.34%.  Then the following  call
to printf will print it for you:
printf("%.2d.%.2d", i/100, i%100); 
 
- Variables
C  has a feature that is a gift from heaven for  programmers  who
code for speed.  This feature is called register variables.  This
is a special storage class which gives the compiler a strong hint
that the variable is heavily used.  Even when accessing an  array
which  is  declared as a global variable,  put a pointer  to  the
array  in a register variable and use the register  variable  for
successive references to the array.
Try to choose you variables as 'small' as possible. If you can do
the  operation with a short int  instead of an int  then  do  it.
Same case for long  and int .
Use  as less floating point variables as possible for the  reason
mentioned before.
To  initialize  an  array  of 1000 characters  to  zero  see  the
following  function  a  fast way.  Remember that an  array  of  4
characters in most C-implementations has the length of a long, so
to  initialize  all  4 characters to zero  it  is  sufficient  to
initialize the long to zero.
int fill(array)
register long *array;
{ register short i;
  for (i = 249; i >= 0; ) array[i--] = 0L;

}

Use as much post-increment and pre-decrement on register pointers
as possible,  since the 68000 has a very efficient way of dealing
with  this  sort  of constructions.  Thus,  we  could  write  the
previous example also as follows:

int fill(array)
register long *array;
{ register short i;
  for (i = 249; i >= 0; ) *array++ = 0L;
}


Also,  when  frequently using a constant,  put the constant in  a
register,  since operations on register variables are faster than
operations on constants.

- Control structures
One control structure which can be very strongly optimized is the
switch-statement. This is done by keeping all values of the case-
labels close to another. Example:

/* not optimal */
switch(var) {
case 1: func1(); break;
case 2: func2(); break;
case 3: func3(); break;
case 4: func4(); break;
case 1000: func1000(); break;
}

/* optimized version */
if (var == 1000) func1000();
else {
     switch(var) {
     case 1: func1(); break;
     case 2: func2(); break;
     case 3: func3(); break;
     case 4: func4(); break;
     }
}


Remember that C has the ?:  operator.  This can be very useful in
conditional function calls. Example:

/* not optimal */
if (var != 0) func("Take hold");
else func("of the flame");

/* optimal */
func( var !=0 ? "Take hold" : "of the flame" );


- Standard functions
Make use of the use standard Kernighan & Ritchie  C-functions but
do  it with care.  One of the most used functions is  undoubtedly
printf .  There is one thing to remember about printf:  it  scans
the  string character by character and internally builds  another
string which it writes to the screen (standard output).  So  when
you want to write a string to the screen you should be aware that
puts  also exists. puts  doesn't have to build an internal string
which  it  can write to the screen,  since its  argument  already
contains the string.  Same is true for fprintf  and fputs , scanf
and gets .

Disclaimer
The text of the articles is identical to the originals like they appeared in old ST NEWS issues. Please take into consideration that the author(s) was (were) a lot younger and less responsible back then. So bad jokes, bad English, youthful arrogance, insults, bravura, over-crediting and tastelessness should be taken with at least a grain of salt. Any contact and/or payment information, as well as deadlines/release dates of any kind should be regarded as outdated. Due to the fact that these pages are not actually contained in an Atari executable here, references to scroll texts, featured demo screens and hidden articles may also be irrelevant.