Asynchronous IO
Volume Number: 12
Issue Number: 12
Column Tag: Toolbox Techniques
Building Better Applications
Via Asynchronous I/O
By Richard Clark, General Magic, Inc.
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
Have you ever looked at an application and wondered how to make it faster? Sure, you
can select better algorithms or rewrite sections in assembly language, but sometimes a
fast processor or great algorithm is not enough. Many applications reach a limit where
they can process the information faster than they can get it. These applications are said
to be I/O bound. Improving such programs is straightforward, once you know
something more about how the Macintosh reads and writes information.
Most developers go through several basic stages in getting information in and out
of their programs. In the first stage, they use their programming language’s built-in
I/O commands - printf and scanf for C, WRITELN and READLN for Pascal. Soon, driven
by the derision of their peers, a desire to manipulate something other than text
streams, or a feeling they should be using the underlying operating system directly,
they will shift over to the Macintosh FSWrite and FSRead routines.
Quite a few Macintosh programmers spend the remainder of their careers using
FSRead and FSWrite. Some use FSRead’s “newline mode” to emulate scanf or READLN.
Others read their data in as needed, whether they need a single character or an entire
structure. The wisest users of FSRead use buffering - they read the data in large
blocks and process the information in memory.
All of these techniques have one property in common - they all use “synchronous
I/O.” A synchronous I/O operation makes the calling program wait until the operation
has been completed. Programmers who want to get the best possible performance out of
their applications can eliminate this wait by switching to “asynchronous I/O” which
asks the OS to transfer information at the same time the other code is running. There is
another reason why advanced Macintosh programmers use asynchronous I/O - it’s the
only way to get at some of the more advanced communications features such as TCP/IP
and to get real-time information from users.
A Programmer’s Look at I/O
We will take a look at the uses of synchronous and asynchronous I/O through a
function that counts occurrences of the letter “A” in a text file. The simplest version
of this program uses the C Standard I/O Library functions.
int countChars(FSSpecPtr fsp)
{
// Count the number of times the letter A appears in the file
FILE *f = NULL;
int counter = 0;
char currChar;
char filename[64];
// Homemade PtoCstr operation which makes a copy of the string
BlockMove((Ptr)&fsp->name[1], filename, fsp->name[0]);
filename[fsp->name[0]] = ‘\0’;
// Count the characters
f = fopen(filename, “r”);
while ((currChar = fgetc(f)) != EOF) {
if (currChar == ‘A’) counter += 1;
}
fclose(f);
return counter;
}
While this looks like a simple program, quite a bit is going on behind the scenes.
fgetc() does not simply read each character from the disk as the program requests it,
but uses a buffering scheme instead. When buffering, the application (or library)
reads a block of information into memory all at once, then returns each item from that
block of memory. Without buffering, each read would have to position the disk’s
read/write head to the proper location on the disk, then wait for the correct area of the
disk to rotate into place. Thus the program would spend most of its time waiting for the
drive hardware itself.
Even with buffering, most Standard I/O library implementations are not as fast
as going directly to the machine’s own file system. The extra bookkeeping associated
with tracking an arbitrary number of files slows things down. We can write a faster
program using the “high level” File Manager calls. When we build our new program,