home *** CD-ROM | disk | FTP | other *** search
/ World of Shareware - Software Farm 2 / wosw_2.zip / wosw_2 / CPROG / OPTC38.ZIP / HINTS.DOC
Text File  |  1992-07-12  |  32KB  |  759 lines

  1.  
  2.         HINTS FOR EFFICIENT PROGRAMMING
  3.         Copyright (c) 1992 by Omega Point, Inc.
  4.            Author: Ratko V. Tomic
  5.  
  6.  
  7. -------------------------------------------------------------------------
  8. NOTE: The text below is taken from the CodeRunneR (R) manual. CodeRunneR
  9.       is a TSR library for C and assembly programmers, thus many hints
  10.       deal implicitly with size optimization. For more information about
  11.       CodeRunneR call Omega Point (508) 877-1819 or fax (508) 877-0915.
  12. -------------------------------------------------------------------------
  13.  
  14. CodeRunneR was designed with the greatest attention for efficiency (size
  15. and speed), all functions were carefully crafted in assembly language.
  16. Combined with some care in the C code you can produce programs rivaling
  17. in size to average assembly language programs, especially on medium and
  18. large projects. On very small programs (few hundred bytes), the assembly
  19. language will still be ahead. In terms of speed, the video functions
  20. (at least) should be competitive with pure assembly code.
  21.  
  22. The C programming hints below have been found useful in reducing the
  23. efficiency gap between C and assembly code.
  24.  
  25. 1. INT or CHAR - Current compilers are notoriously bad in handling 
  26.    expressions involving byte size data. The reason for this is an
  27.    overly literal interpretation of C "abstract machine", which mandates
  28.    that the result should be evaluated as if extended to integer. On the
  29.    other hand, the xxx86 processors have a rich subset of instructions
  30.    operating on byte size items. These instructions make great majority
  31.    of "integral promotions" unnecessary. Unfortunately, only a small
  32.    fraction of these are utilized by C compilers, resulting in much more
  33.    expensive operations on char versus int variables. Thus a saving of
  34.    one byte for the variable storage is practically always more than 
  35.    paid for in code size.
  36.    
  37.    The following rules have been found useful in reducing this type 
  38.    of inefficiency:
  39.  
  40.    a) If a char variable is used within expressions, or is being passed to
  41.       functions, or returned by functions, it is better to use int instead.
  42.       This is true even in case when variable is only used as TRUE/FALSE
  43.       flag. If defined as an int, the variable will occupy 1 more byte,
  44.       but for each use one or more bytes are saved in generated code.
  45.       In addition the speed is often improved due to an even address
  46.       alignment of variables, preferred by the 16/32 bit CPU-s.
  47.  
  48.    b) In some situation rule (a) cannot be applied, since an array of
  49.       characters is the source for byte size variables. In that case
  50.       the characters should be cast as SIGNED whenever possible since
  51.       the instruction CBW used to extend signed char to int is 1 byte,
  52.       while the instruction MOV AH,0 used for unsigned is 2 bytes.
  53.  
  54.    c) CodeRunner functions which receive char or byte as an argument
  55.       will ignore high byte passed. Hence you can safely pass integers
  56.       without having to mask out high byte. This also allows application
  57.       of the rule (b) independently of char being below or above 0x80.
  58.  
  59.    d) Auto variables of char type will occupy two bytes on stack
  60.       anyways, hence one should always gain by using int instead.
  61.  
  62.    e) If an integer variable is to be compared to a char, or passed to
  63.       function expecting char, casting integer to char is better than
  64.       AND-ing it with 0xff.
  65.  
  66.  
  67. 2. INITIALIZED AUTO variables for strings, arrays, structures or any
  68.    composite objects are very inefficient. Each such object will
  69.    occupy exactly twice the size defined. Also, the compiler will
  70.    on every function entry call an internal transfer function to
  71.    copy the variable from original place into the auto variable.
  72.    Besides wasting time, this transfer function is not usable by
  73.    the C code as a memory copy function. And lastly, the space
  74.    used as the destination is also internal space (stack space),
  75.    which will often place much heavier demand on stack size. You
  76.    can often create a "local copy" of such item in some temporarily
  77.    free buffer (e.g. a disk i/o buffer when disk i/o is idle).
  78.  
  79.    a) If this type of variables is truly needed (rare case), one may
  80.       use other general purpose memory copy function (like memcpy())
  81.       which is usable for other purposes as well. In case of Turbo C
  82.       this is even more important, since Turbo C uses far call and
  83.       32 bit pointers to perform the copying, even in small model.
  84.  
  85.    b) In most situations, the intention was only to keep a variable
  86.       local to the function and not to reinitialize variable each time
  87.       function is activated. In that case the variable should be turned
  88.       into static, accomplishing same result in half the size.
  89.  
  90.       Hence instead of:
  91.  
  92.         func() 
  93.         { 
  94.         char msg[]="This is an initialized local string.";
  95.         }
  96.  
  97.       it is better to use:
  98.  
  99.         func()
  100.         {
  101.         static char msg[]="This is an initialized local string.";
  102.         }
  103.  
  104.      This is one example where ANSI C standard has violated a useful
  105.      classic C rule that more efficient constructs are shorter to type.
  106.  
  107.  
  108. 3. Another case of the same Classic C rule violation is the passing of
  109.    structures by value to/from functions. Such deceivingly efficient
  110.    constructs are wasteful on memory and time even when implemented
  111.    well, and especially in the current C compilers where they were added
  112.    as a marketing feature to boost the count of ANSI compliance points.
  113.  
  114.    Hence instead of:
  115.  
  116.      struct x_type func(struct x_type x)
  117.      {
  118.        ....
  119.        process(x.field);
  120.        ....
  121.        return(x);
  122.        ....
  123.      }
  124.  
  125.    it is more efficient to use:
  126.  
  127.      void funct(struct x_type *x)
  128.      {
  129.        ...
  130.        process(x->field);
  131.        ...
  132.        return;
  133.       }
  134.  
  135.    In rare circumstances, where a local copy of a structure is needed in
  136.    order to be modified while preserving the callers copy, it is still
  137.    better to use a general purpose memory copy, rather than have compiler
  138.    link in an internal-use-only transfer function.
  139.  
  140. 4. Uninitialized AUTO variables are more efficient than the equivalent
  141.    static/global variables both in memory (typically 1 byte per access)
  142.    and speed. Additionally, the space used by auto variables is released
  143.    when function exits. Therefore both simple and composite objects
  144.    needed temporarily and locally should be defined as auto variables.
  145.  
  146.    Similar situation is with arguments a function receives - they are
  147.    equivalent to auto variables in efficiency, thus they are often 
  148.    better than global variables.
  149.  
  150. 5. Variables which are OFTEN being passed as arguments between functions
  151.    should be redefined as globals, ar at least static for that module.
  152.    This rule needs to be counterbalanced with the previous rule - the
  153.    decision parameter is "often". Namely each access to a global/static
  154.    costs on average 1 extra byte (compared to access of a function
  155.    argument), but the cost of passing such variable is on average 5 bytes.
  156.    
  157. 6. If a variable occurs in the code as say: n-1, two or more times, it
  158.    is better to define another variable, n1=n-1 and use n1 instead.
  159.    Just the code space saved on the single re-use pays for the
  160.    extra space of variable n1. The program speed will benefit too.
  161.    This rule is applicable to char, int, long variables as well as
  162.    pointers to any objects. More complex expressions (than n-1)
  163.    will benefit even more.
  164.  
  165.    Note also that the assignment (n1=n-1; above) should be combined
  166.    with the first use of (n-1):
  167.  
  168.    For example instead of:
  169.  
  170.      y = f(x) + n - 1;
  171.      z = v * (n-1);
  172.  
  173.    use:
  174.  
  175.      y = f(x) + (n1=n-1);
  176.      z = v * n1;
  177.  
  178.  
  179. 7. 2-D arrays are not efficiently handled by C compilers. Each access
  180.    will produce multiply instruction to compute an offset into the array.
  181.    If a 2-D array is used mostly in a sequential manner (like screens,
  182.    edit buffers, matrices, etc.) one should define them as 1-D arrays
  183.    and compute starting offset explicitly, and from then on use ++,--
  184.    operators (on either pointers or indices).
  185.    
  186. 8. Larger expressions are compiled more efficiently than the equivalent
  187.    step-by-step sequence of expressions. This is also true for a function
  188.    invocation within an argument of another function invocation. The
  189.    reason for this is the freedom compilers have when evaluating
  190.    expressions - compiler need not worry about side effects occurring
  191.    during evaluation, their sequencing is in most cases undefined, thus
  192.    compilers can ignore aliasing. On the other hand, the equivalent
  193.    sequence of simple expressions has so called "sequence points" after
  194.    each step, requiring often that the intermediate results be stored
  195.    in memory. In other words the sequence of small expressions places
  196.    additional, often useless, constraints on evaluation.
  197.  
  198.    The drawbacks to be weighed in a decision are higher susceptibility
  199.    to errors, lower readability and lower flexibility of complex expressions.
  200.  
  201. 9. Functions which accept string pointers and their lengths as arguments,
  202.    should have lengths placed before pointers. This is advantageous when
  203.    the following invocation method is used:
  204.  
  205.    char *s;
  206.    ....
  207.      str_func( str_len(s), s);
  208.    ....
  209.  
  210.     The reason for this is the order in which arguments are pushed on the
  211.     stack - in case of C parameter convention this is from right to left.
  212.     Thus in the example above, the code will fetch the value s and push
  213.     it twice than call str_len(). Had the argument order been opposite,
  214.     the value s would be accessed (computed) twice.
  215.  
  216. 10. When segments and offsets are defined within structures, offset should
  217.     be placed first, followed by the segment. When passing far pointers
  218.     as a separate words for offset and segment, offset should be first.
  219.     The target function should receive this pair of words as the far
  220.     pointer, and will access it using single instruction (LES/LDS).
  221.  
  222. 11. If the total amount of data used by program exceeds 64K, the bulk of
  223.     this space is normally occupied by arrays (programs with 30,000 simple
  224.     variables are rare). The largest of these arrays should be moved into
  225.     far heap (see section on (far) memory allocation). This is always more
  226.     efficient than switching globally to large-data models.
  227.  
  228.     CodeRunner is compiled in small data model, allowing for 64K of data
  229.     and 64K of code using near access, and up to memory size of far data.
  230.     The library functions can move data to/from far memory. One can also
  231.     use far pointers available in Turbo and MS C compilers (mixed models).
  232.  
  233. 12. Accessing array elements via pointers (as opposed to index) is more 
  234.     efficient, especially if arrays have int elements (or larger).
  235.     This only holds if the pointer is used without additional offset,
  236.     i.e. as *p instead of *(p+i) or p[i].
  237.  
  238. 13. If an index is used to access array elements, global arrays
  239.     will compile more efficiently than arrays defined via starting
  240.     pointer. Hence if an alternatives are p[i] and a[i] (p is a
  241.     pointer, a[] is an array), the array is preferable. This should not
  242.     be confused with the "abstract C machine" which mandates that array
  243.     references are converted to pointers - the actual code does make
  244.     distinction. The reason is the same as for the constants being
  245.     more efficient than variables - in this case a[] is a constant,
  246.     while p is a variable, both representing some memory address.
  247.  
  248.     One can unify this and the preceding rules as: The pointers are
  249.     more efficient than arrays if they follow the array elements,
  250.     and less efficient if they are fixed (a variable playing role
  251.     of a constant).
  252.  
  253. 14. Loops which always execute at least once, are best implemented
  254.     as do {...} while() loops, as opposed to for() {...} loops or
  255.     while() {...} loops. The saving is 2-3 bytes.
  256.  
  257. 15. Switch statement is often implemented more efficiently using two
  258.     arrays - one with words (or preferably bytes) containing case values,
  259.     and another array containing function pointers for the matching cases.
  260.     There are three reasons for this:
  261.  
  262.     A. Switch statement will often generate code that does exactly
  263.        same thing: it has the two arrays, both with 16-bit elements
  264.        plus the code which scans the first one (case values) then
  265.        dispatches from the second one. You can not only use common
  266.        search function, but also often reduce the case values to 8 bits.
  267.  
  268.     B. Actions done for particular case can be delegated to functions
  269.        which are reusable in other switch statements or other code.
  270.  
  271.     C. For each case there will be a jump code to join the common
  272.        end point of the switch statement - this is 2-3 byte instruction.
  273.        With the suggested replacement, the RET instruction is used
  274.        which is always 1 byte.
  275.  
  276.     This method also has a side benefit of allowing variables for case
  277.     labels. Within the context of CodeRunner, this method is useful for
  278.     hot-key response. The hot-key service routine receives as an argument
  279.     the hot-key which has triggered TSR activation. The CR.LIB function
  280.     word_pos() when applied to hot-key and the array of all hot-keys
  281.     will return 1-based index of the hot-key in the array. The matching
  282.     array of function pointers should have first pointer set
  283.     to an error function (no match case), and the rest in the same
  284.     order as the hot-key array. The whole decision process could
  285.     look as follows:
  286.  
  287.     word hot_key_list[] = {  key1, key2, key3 }; /* Hot keys          */
  288.     fp dispatch[]={err_func,func1,func2,func3 }; /* Function pointers */
  289.  
  290.     hot_key_service(key)
  291.     {
  292.      .....
  293.      dispatch[word_pos(key,hot_key_list,3)]();
  294.      .....
  295.     }
  296.  
  297.     When a switch statement is coded in this manner, the err_func()
  298.     should replace the default case of the switch statement.
  299.  
  300.     As usually there is a tradeoff involved - the switch statement allows
  301.     access of auto variables belonging to the same function. If these
  302.     have to be recoded as globals, or passed to individual case functions
  303.     the overhead for globals (or function entry frame setup) may offset 
  304.     the benefits.
  305.  
  306. 16. When left to choose register variables, compilers will generally
  307.     pick the first two integer variables. You may override the default
  308.     choice by weighing space/time priorities. If the space is important
  309.     select variables which occur (literally) most frequently within a
  310.     function. If the speed is important (and you don't use assembler)
  311.     select variables used most frequently in the inner-most loop within
  312.     a function.
  313.  
  314. 17. For pop-up applications, compiler should be set to optimize for size,
  315.     since the bulk of time consuming (low level) screen handling is done
  316.     by the CodeRunner functions. In some cases the size optimization
  317.     will actually produce faster code than speed optimization due to
  318.     compilers disregard of CPU prefetch queue effects. And in almost
  319.     all cases the speed difference will be beyond human perception.
  320.  
  321. 18. Compiler switch for stack-overflow checking should be disabled for
  322.     TSR application. Namely, the normal action of the compiler will
  323.     be to abort the program and exit to DOS. This obviously will
  324.     not work once program goes resident, since officially the program
  325.     has already "exited" once. Thus all the checking code is at best
  326.     useless in this environment (CodeRunneR library substitutes checking
  327.     and exit function with a neutral function). An alternate approach
  328.     is offered by a pair of CodeRunner functions _watch_stack() and
  329.     _unused_stack(). These functions are intended for the test stage
  330.     of development, when they can help determine minimal, but still
  331.     safe stack size. They should be removed from the final program.
  332.  
  333. 19. When selecting functions from the library, if there is a tossup
  334.     choice between the two functions, you should select one already
  335.     used somewhere else in the code (or at least the one more likely
  336.     usable elsewhere). When the choice is not a tossup, but requires
  337.     extra C code to make it use the same function, one should use the
  338.     best suited library function. In other words, one should avoid
  339.     variety used for variety sake.
  340.  
  341. 20. The CodeRunner video functions are fast enough to make redisplaying
  342.     of a line or a window an acceptable alternative to trying to repaint 
  343.     only the changed portions of the screen. This can save a great deal
  344.     of code space.
  345.  
  346. 21. For the same reason as in previous rule, the nested windows may be 
  347.     implemented without buffers to save an overlayed portion of another
  348.     window. Calling the same function which drew the overlayed window to
  349.     start with, will save on the data space. In case of two levels of
  350.     nesting, a swap_blk() functions may be used to swap the screen block
  351.     and another buffer.
  352.  
  353. 22. Goto statement is still legal in C language. Your C compiler will
  354.     never generate more than 3 bytes per goto (most often just 2).
  355.     In the same time, even a single variable used to control the early
  356.     exit from nested loops will generate 4 bytes to get set and 4-5
  357.     bytes for each test, and then 2-3 bytes to jump anyways.
  358.  
  359. 23. CodeRunneR's data compaction will not dispose strings specified
  360.     as function arguments. These strings should be defined in the
  361.     disposable data module, and accessed as an external variable
  362.     from the disposable code module.
  363.  
  364.     For example, instead of:
  365.  
  366.       main()
  367.       {
  368.      dsp("Signon Screen");  /* This string is not disposable */
  369.       }
  370.  
  371.     it is more efficient to use:
  372.  
  373.       extern char msg[]; /* Defined in disposable data module */
  374.  
  375.       main()
  376.       {
  377.         dsp(msg);
  378.       }
  379.  
  380.     In assembler you can just define the string within the same module,
  381.     but in _IDATA segment to make it disposable.
  382.  
  383. 24. Educational C texts providing lists of DO-s and DON'T-s for portable,
  384.     modular, readable, structured and object-oriented coding practices
  385.     are a rich source of DON'T-s and DO-s for efficient programs.
  386.  
  387.     This advice should be taken with grain of salt. Namely, when sufficient
  388.     effort has been spent polishing a program and when it looks that no 
  389.     more improvements are possible using "noble" methods, the point of 
  390.     tradeoffs has been reached. Only at this point, if further size/speed
  391.     improvements are needed, one should look at the DON'T-s from such
  392.     lists (or DO-s in your code) and choose the least of evils which will
  393.     help achieve the desired performance.
  394.  
  395.     Violation of the good practices before the tradeoff point is sloppy
  396.     rather than efficient programming.
  397.  
  398.  
  399. 25.  ELSE statement generates a JMP instruction around the ELSE-block.
  400.      This can be eliminated as shown in the following example:
  401.  
  402.      Instead of:
  403.  
  404.         if (x<y) z=10;
  405.       else z=0;
  406.  
  407.      Use more compact form:
  408.  
  409.     z=0;
  410.     if (x<y) z=10;
  411.  
  412.      NOTE: This also applies to ?: operator, which is just a compactly
  413.      written variation of IF-ELSE statement.
  414.  
  415. 26.  ELSE IF statement may often be skipped gaining in space at the
  416.      expense of the execution speed. This is especially useful when the
  417.      statements within each conditional block are simple assignment or
  418.      single integer operator like +,-,^,|,&,++,--. In such case there is
  419.      almost no speed penalty for the space gained.
  420.  
  421.      For example:
  422.  
  423.     if ((c>='a')&&(c<='z')) c-=0x20;
  424.       else if (c<0x20) c=0x20;
  425.  
  426.      can be replaced with seemingly redundant, but more compact check:
  427.  
  428.     if ((c>='a')&&(c<='z')) c-=0x20;
  429.     if (c<0x20) c=0x20;
  430.  
  431.      This type of savings is especially useful in keystroke processing code
  432.      which, due to human typing speed, need not be very fast.
  433.  
  434.      In addition to greater compactness, the decoupling of conditional
  435.      statements makes code simpler to shuffle around.
  436.  
  437.  
  438. 27.  EXTRACTION of common expressions into functions can often eliminate
  439.      lots of repetitive expressions. Simple call to a function with no
  440.      arguments takes only 3 bytes. Any simple assignment will take at least
  441.      that much (usually more). Therefore, any pair of assignments
  442.      which repeats in the code 2 or more times can be made into a function.
  443.  
  444.      For example:
  445.  
  446.     ...
  447.     crs_x=X0; crs_y=Y0;    /* Put cursor in the corner */
  448.     ...
  449.     crs_x=X0; crs_y=Y0;    /* Reset cursor to the same corner */
  450.     ...
  451.  
  452.      can be done in less space using:
  453.  
  454.     ...
  455.     corner0 ();
  456.     ...
  457.     corner0 ();
  458.     ...
  459.  
  460.       corner0 ()
  461.     {
  462.     crs_x=X0; crs_y=Y0;
  463.     }
  464.  
  465.      This example will save space even if Y0 is not used, and X0 is some
  466.      global variable.
  467.  
  468.  
  469. 28.  The previous hint is an example of TIME/SPACE tradeoff. One can
  470.      generalize the simple case above to any piece of code used more than
  471.      once within a program. Such space optimizations are often done after
  472.      the program is already finished, and can be done almost mechanically.
  473.      Pieces of code which are similar, can be often made identical and
  474.      then replaced with a function. Variables originally placed arbitrarily
  475.      can be reorganized into records, allowing for simpler argument passing
  476.      to common functions. 
  477.  
  478.      In interactive applications, the response speed faster than the screen
  479.      refresh time (around 20 ms) is without any visible effect. Since the
  480.      function replacement method described above adds under 20 microseconds
  481.      even on a 4.77 Mhz IBM PC, then fifty such replacements will add only
  482.      one millisecond to the execution time, which is humanly unperceptible
  483.      difference. On the other hand, few hundred bytes saved are certainly
  484.      perceptible with ever growing DOS with each new version.
  485.  
  486.      The function-blocking method has an additional benefit that the code
  487.      is easier to read and modify, especially if function names are
  488.      well chosen. The resulting code will look as if designed top-down,
  489.      without having to think through entire program up-front.
  490.  
  491.  
  492. 29.  EXAMINE GENERATED CODE by making compiler produce ASSEMBLER output.
  493.      Most of todays C compilers have a switch to generate ASM file (-S for
  494.      Turbo C and /Fa for Microsoft C). If you are familiar with assembly
  495.      language, this exploration will show which constructs need
  496.      improvements. Typically, you would look for expressions in which
  497.      compiler extends char or byte to int or word (instructions like
  498.      CBW or MOV AH,0 or XOR AH,AH). Such expressions can usually be
  499.      typecast so that no extension instructions are generated.
  500.      Other things to watch are Jumps-Around-Jumps necessitated by
  501.      large conditional statement blocks. Reducing the size of these
  502.      blocks by redistributing some work-load to functions will produce
  503.      smaller and cleaner code (perhaps slightly slower).
  504.  
  505. 30.  Arithmetic expressions can often beneficially replace IF/ELSE 
  506.      decisions.  Consider case of PING-PONG buffers: A pointer (char*)p
  507.      is toggled between buffers buf1 and buf2. The switching of the
  508.      pointer is usually done as:
  509.  
  510.     if (p==buf1) p=buf2;
  511.       else p=buf1;
  512.  
  513.      More efficient construct is:
  514.  
  515.     p = (char*) ((word)p ^ ((word)buf1 ^ (word)buf2));
  516.  
  517.      NOTE: The cast to word is done to allow use of ^ operator which is
  518.      generally not defined on pointers. In this case it is well defined.
  519.      In practical use the buf1^buf2 would be a constant computed in
  520.      disposable code.
  521.  
  522.      This XOR trick can be used whenever some (integer) variable x needs to
  523.      be toggled between two specified values a and b. You simply create
  524.      variable ab=a^b, and toggle x via x^=ab; .
  525.  
  526.      In many cases the control variables could be defined in such way
  527.      that the plain arithmetic could be applied to produce desired
  528.      result. 
  529.  
  530. 31.  Along the lines of previous example, you should check the bit
  531.      patterns of variables occurring in the program. There are often
  532.      surprising properties of binary 2-complement arithmetic which can
  533.      greatly optimize the code size and speed.
  534.  
  535.      A trivial example is replacement of say (x % 8) with (x & 7), or
  536.      the same way for any power of two.
  537.  
  538.      Less obvious is, for example, the task of extracting the bit mask
  539.      for the right-most bit set in a word.
  540.   
  541.      word x,mask;
  542.  
  543.      Instead of:
  544.  
  545.           mask=1;
  546.           do if (mask & x) break;  /* Test if non-zero bit found */
  547.             while ((mask<<=1)!=0);
  548.  
  549.      Use:
  550.           mask = -x & x;
  551.  
  552.  
  553.      Similarly if you are checking if a non-zero integer is power of two 
  554.      (i.e. it has exactly 1 bit set) you can use:
  555.  
  556.      #define is_power_of_2(x)  ( (x) & ((x)-1) )
  557.  
  558.      Thus to count bits in a word x, instead of checking all bits via
  559.      shifting a bit mask, you can use the following:
  560.  
  561.      for (cnt=0; x; ++cnt) x &= x-1;
  562.  
  563.      This will produce bit count (cnt) on average twice as fast as the
  564.      simple shift and test method.
  565.  
  566.      NOTE: The expressions above work for signed and unsigned integral
  567.      values (char, int, long). 
  568.  
  569.  
  570. 32.  Integer arithmetic in C is modular (also in assembler). Although
  571.      the ANSI C guarantees this only for unsigned types, in practice
  572.      on most processors (including 8086,68000,...) this is also true 
  573.      for signed integer variables (consequence of the binary 2-complement
  574.      representation).
  575.  
  576.      This can be most often used when a bit-pattern is shifted to the
  577.      left (also to the right for unsigned) to eliminate additional
  578.      count variable set to 8, 16 or 32.
  579.  
  580.      Thus instead of:
  581.  
  582.          for (i=0,mask=1; i<16; i++,mask<<=1) {...}
  583.  
  584.      you can often use (assuming you need a mask):
  585.  
  586.          mask=1;
  587.          do {
  588.          ...
  589.          } while ((mask<<=1)!=0);
  590.  
  591.      This way the mask doubles as a bit mask and as a counter.
  592.  
  593.      NOTE: We have also applied above the rule for loops which always
  594.            execute at least once, therefore do-while form is used.
  595.  
  596.  
  597. 33.  Another consequence of modular arithmetic is that pointer/index
  598.      overflow around 64K can be checked without resorting to long
  599.      arithmetic.
  600.  
  601.      Thus instead of:
  602.  
  603.        char *p;
  604.        word step; /* Assumed forward step */
  605.  
  606.          if (((long)p+step)<0x10000L) p+=step;
  607.  
  608.     You can use:
  609.  
  610.          if (p+step>p) p+=step;
  611.  
  612.     In case of backward step (check for wrap around 0) you can use:
  613.  
  614.          if (p-step<p) p-=step;
  615.  
  616.  
  617. 35.  You can avoid long arithmetic when subtracting from 64K.
  618.      
  619.      Thus instead:
  620.  
  621.        word used,free;
  622.          ...
  623.          free = 0x10000L-(long)used;
  624.          ...
  625.  
  626.      you can do:
  627.  
  628.          free = -used;
  629.  
  630.  
  631. 34.  Loop counters are more efficient when counting down to 0 rather than
  632.      from 0 up.
  633.  
  634.      Hence instead of:
  635.  
  636.         i=0; do {....} while (++i<N);
  637.  
  638.      it is better to use (whenever possible):
  639.  
  640.         i=N; do {....} while (--i);
  641.  
  642.      The latter often exploits CPU zero flag when decrementing i.
  643.      The more recent C compilers also utilize LOOP instruction.
  644.  
  645.  
  646. 35.  Pre-increment/decrement operators are often more efficient than the
  647.      post-increment/decrement version. This is valid mostly when these
  648.      operators are combined in the expressions using compare (like ++i<N),
  649.      and has no effect if the simple ++i; or --i; is done (without compare).
  650.      The reason for this is that the processor flags are changed by ++ or --,
  651.      thus the (i++ < N) must generate an extra code (usually a JMP) to 
  652.      prevent ++ from destroying the result of compare.
  653.  
  654.  
  655. 36.  Function argument passing has dual overhead - an overhead for pushing
  656.      each argument on the stack, plus an overhead for clearing arguments
  657.      from the stack. The latter is 3 bytes for MSC for any number of
  658.      arguments (2 bytes for 1 argument in recent versions), but for Turbo C
  659.      it is 1,2 or 3 bytes for 1,2 or 3 and more arguments, respectively.
  660.      Thus to reduce stack clearing overhead it is useful (especially for TC)
  661.      to keep number of arguments to 1 or 2. Of course, any reduction helps
  662.      also in the argument pushing phase.
  663.  
  664.      One way to reduce number of arguments is to replace them with globals.
  665.      As mentioned earlier, this has a drawback of more expensive global
  666.      (or static) variable access. Another way is to use structures (or
  667.      arrays) which hold several variables, and pass only the pointer to
  668.      the structure (array).  The third method is useful when several
  669.      arguments are passed via a chain of nested calls.
  670.  
  671.      For example instead of:
  672.  
  673.        func1(int a,int b,int c,int d)
  674.        {
  675.           ....
  676.           func2(b,c,d);
  677.           ....
  678.        }
  679.        
  680.        func2(int x,int y,int z) {...}
  681.  
  682.  
  683.      you can use:
  684.  
  685.        func1(int a,int b,int c,int d)
  686.        {
  687.           ...
  688.           func2(&b);
  689.           ...
  690.        }
  691.  
  692.        func2(int *p)
  693.        {
  694.        /***  x is p[0], y is p[1] and z is p[2] ***/
  695.        }
  696.  
  697.     Structures can be in the same manner for more complicated argument
  698.     lists (or for code documentation purposes).
  699.  
  700.     NOTE: You should not use this for independent auto variables inside
  701.           the func1(), such as int e,f,g; since the variables are not
  702.           necessarily allocated in the order of declaration. On the
  703.           other hand, the passing of arguments to functions is
  704.           additionally constrained with the CPU stack discipline for
  705.           C convention calls, thus it has well defined behavior across
  706.           MS-DOS C compilers.
  707.  
  708.  
  709. 37. If there are 2 or more occurences of some function call, check the
  710.     sourrounding statements. There are aften common pieces of code just
  711.     before (or after) the call. The common section should be moved into
  712.     the function. If the surrounding code is similar, one can often
  713.     modify it by replacing some auto variable with a global, thus making 
  714.     it identical so that it can be moved to the function.
  715.  
  716.     If the code surrounding the call appears in the same form in several,
  717.     but not all occurences consider what effect it would have to move it
  718.     into the function anyways. Often the places which don't have the
  719.     common piece are not affected by such code being executed, thus it
  720.     can be safely moved into the function.
  721.  
  722.     The gain with this rule is even greater than the one from hint #27
  723.     since the call already exists.
  724.  
  725.  
  726. 38. A switch statement may have two or more cases which are similar
  727.     except for some variables being assigned different value. The
  728.     similar case-blocks can be made to reuse same code (i.e. fall thru)
  729.     by initializing the variables to one of the cases and modifying
  730.     it in the remaining cases. 
  731.  
  732.     For example this code:
  733.  
  734.       switch (i)
  735.         {
  736.         ...
  737.         case KEY0:   func(0,a,b,c);
  738.                      break;
  739.         case KEY1:   func(1,a,b,c);
  740.                      break;
  741.         case KEY2:   func(2,a,b,c);
  742.                      break;
  743.         ...
  744.         }
  745.  
  746.      can be optimized (for size) as follows:
  747.  
  748.       x=0;
  749.       switch (i)
  750.         {
  751.         ...
  752.         case KEY2:   x++;
  753.         case KEY1:   x++;
  754.         case KEY0:   func(x,a,b,c);
  755.                      break;
  756.         ...
  757.         }
  758.  
  759.