174 lines
7.8 KiB
Markdown
174 lines
7.8 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](01-04.html) [Table of Contents](index.html) [Next](01-06.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
The third reason is often fallacious. C library functions are not always
|
||
written in assembly, nor are they always particularly well-optimized.
|
||
(In fact, they’re often written for *portability*, which has nothing to
|
||
do with optimization.) What’s more, they’re general-purpose functions,
|
||
and often can be outperformed by well-but-not- brilliantly-written code
|
||
that is well-matched to a specific task. As an example, consider Listing
|
||
1.5, which uses internal buffering to handle blocks of bytes at a time.
|
||
Table 1.1 shows that Listing 1.5 is 2.5 to 4 times faster than Listing
|
||
1.4 (and as much as 49 times faster than Listing 1.1!), even though it
|
||
uses no assembly at all.
|
||
|
||
------------------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||
 *Clearly, you can do well by using special-purpose C code in place of a C library function—if you have a thorough understanding of how the C library function operates and exactly what your application needs done. Otherwise, you’ll end up rewriting C library functions in C, which makes no sense at all.*
|
||
------------------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
**LISTING 1.5 L1-5.C**
|
||
|
||
/*
|
||
* Program to calculate the 16-bit checksum of the stream of bytes
|
||
* from the specified file. Buffers the bytes internally, rather
|
||
* than letting C or DOS do the work.
|
||
*/
|
||
#include <stdio.h>
|
||
#include <fcntl.h>
|
||
#include <alloc.h> /* alloc.h for Borland,
|
||
malloc.h for Microsoft */
|
||
|
||
#define BUFFER_SIZE 0x8000 /* 32Kb data buffer */
|
||
|
||
main(int argc, char *argv[]) {
|
||
int Handle;
|
||
unsigned int Checksum;
|
||
unsigned char *WorkingBuffer, *WorkingPtr;
|
||
int WorkingLength, LengthCount;
|
||
|
||
if ( argc != 2 ) {
|
||
printf(“usage: checksum filename\n”);
|
||
exit(1);
|
||
}
|
||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||
printf(“Can’t open file: %s\n”, argv[1]);
|
||
exit(1);
|
||
}
|
||
|
||
/* Get memory in which to buffer the data */
|
||
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
||
printf(“Can’t get enough memory\n”);
|
||
exit(1);
|
||
}
|
||
|
||
/* Initialize the checksum accumulator */
|
||
Checksum = 0;
|
||
|
||
/* Process the file in BUFFER_SIZE chunks */
|
||
do {
|
||
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
||
BUFFER_SIZE)) == -1 ) {
|
||
printf(“Error reading file %s\n”, argv[1]);
|
||
exit(1);
|
||
}
|
||
/* Checksum this chunk */
|
||
WorkingPtr = WorkingBuffer;
|
||
LengthCount = WorkingLength;
|
||
while ( LengthCount-- ) {
|
||
/* Add each byte in turn into the checksum accumulator */
|
||
Checksum += (unsigned int) *WorkingPtr++;
|
||
}
|
||
} while ( WorkingLength );
|
||
|
||
/* Report the result */
|
||
printf(“The checksum is: %u\n”, Checksum);
|
||
exit(0);
|
||
}
|
||
|
||
That brings us to the fourth reason: avoiding an internal-buffered
|
||
implementation like Listing 1.5 because of the difficulty of coding such
|
||
an approach. True, it is easier to let a C library function do the work,
|
||
but it’s not all that hard to do the buffering internally. The key is
|
||
the concept of handling data in *restartable blocks;* that is, reading a
|
||
chunk of data, operating on the data until it runs out, suspending the
|
||
operation while more data is read in, and then continuing as though
|
||
nothing had happened.
|
||
|
||
In Listing 1.5 the restartable block implementation is pretty simple
|
||
because checksumming works with one byte at a time, forgetting about
|
||
each byte immediately after adding it into the total. Listing 1.5 reads
|
||
in a block of bytes from the file, checksums the bytes in the block, and
|
||
gets another block, repeating the process until the entire file has been
|
||
processed. In Chapter 5, we’ll see a more complex restartable block
|
||
implementation, involving searching for text strings.
|
||
|
||
At any rate, Listing 1.5 isn’t much more complicated than Listing
|
||
1.4—and it’s a *lot* faster. Always consider the alternatives; a bit of
|
||
clever thinking and program redesign can go a long way.
|
||
|
||
#### Know How to Turn On the Juice {#Heading12}
|
||
|
||
I have said time and again that optimization is pointless until the
|
||
design is settled. When that time comes, however, optimization can
|
||
indeed make a significant difference. Table 1.1 indicates that the
|
||
optimized version of Listing 1.5 produced by Microsoft C outperforms an
|
||
unoptimized version of the same code by more than 60 percent. What’s
|
||
more, a mostly-assembly version of Listing 1.5, shown in Listings 1.6
|
||
and 1.7, outperforms even the best-optimized C version of List1.5 by 26
|
||
percent. These are considerable improvements, well worth pursuing—once
|
||
the design has been maxed out.
|
||
|
||
**LISTING 1.6 L1-6.C**
|
||
|
||
/*
|
||
* Program to calculate the 16-bit checksum of the stream of bytes
|
||
* from the specified file. Buffers the bytes internally, rather
|
||
* than letting C or DOS do the work, with the time-critical
|
||
* portion of the code written in optimized assembler.
|
||
*/
|
||
#include <stdio.h>
|
||
#include <fcntl.h>
|
||
#include <alloc.h> /* alloc.h for Borland,
|
||
malloc.h for Microsoft */
|
||
|
||
#define BUFFER_SIZE 0x8000 /* 32K data buffer */
|
||
|
||
main(int argc, char *argv[]) {
|
||
int Handle;
|
||
unsigned int Checksum;
|
||
unsigned char *WorkingBuffer;
|
||
int WorkingLength;
|
||
|
||
if ( argc != 2 ) {
|
||
printf(“usage: checksum filename\n”);
|
||
exit(1);
|
||
}
|
||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||
printf(“Can’t open file: %s\n”, argv[1]);
|
||
exit(1);
|
||
}
|
||
|
||
/* Get memory in which to buffer the data */
|
||
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
||
printf(“Can’t get enough memory\n”);
|
||
exit(1);
|
||
}
|
||
|
||
/* Initialize the checksum accumulator */
|
||
Checksum = 0;
|
||
|
||
/* Process the file in 32K chunks */
|
||
do {
|
||
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
||
BUFFER_SIZE)) == -1 ) {
|
||
printf(“Error reading file %s\n”, argv[1]);
|
||
exit(1);
|
||
}
|
||
/* Checksum this chunk if there’s anything in it */
|
||
if ( WorkingLength )
|
||
ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum);
|
||
} while ( WorkingLength );
|
||
|
||
/* Report the result */
|
||
printf(“The checksum is: %u\n”, Checksum);
|
||
exit(0);
|
||
}
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](01-04.html) [Table of Contents](index.html) [Next](01-06.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|