[fa.info-mac] MacPascal I/O

info-mac@uw-beaver (info-mac) (11/16/84)

From: singer@harvard.ARPA (Andrew Singer)
Concerning Macintosh Pascal I/O:

   I won't argue that I/O couldn't be faster, but understand that when
you're reading and/or writing a character at a time there is considerably
more overhead per byte than if you are reading and/or writing in larger
chunks. This overhead includes not only the raw I/O overhead, but also the
statement interpretation overhead on each Pascal statement to do the I/O
and associated processing.

   In the case of reading MacPaint files, it is not really necessary to
read a character at a time, as the following (crude) program demonstrates:

{ Program to read a picture from a MacPaint file and display a  }
{ scaled-down image (a 4-to-1 reduction) of that picture in the }
{ Drawing window.                                               }
{                                                               }
{ Adapted from an algorithm given by Bill Atkinson in "The      }
{ MacPaint Document Format," a 3-page writeup under "Macintosh  }
{ Misc." in "Inside Macintosh."                                 }

program DisplayPicture;
uses
 QuickDraw2;
const
 UnPackBits = $A8D0;
type
 Block = array[1..256] of Integer;
var
 F : file of Block;
 I, J : Integer;
 Buffer : array[1..2] of Block;
 ScanLine : array[1..144] of Integer;
 SrcPtr, DstPtr : QDPtr;
 G : GrafPtr;
 B : BitMap;
 DstRect : Rect;
 MaskRgn : RgnHandle;
begin
 ShowDrawing;
 GetPort(G);
 B.BaseAddr := @ScanLine;
 B.RowBytes := 72;
 SetRect(B.Bounds, 0, 0, 576, 4);
 SetRect(DstRect,  0, 0, 144, 1);
 MaskRgn := nil;
 Reset(F, OldFileName('Picture File*'));
 FrameRect(-1, -1, 181, 145);
 Get(F);
 for I := 1 to 2 do
  Read(F, Buffer[I]);
  SrcPtr := @Buffer;
  for I := 1 to 180 do
   begin
    DstPtr := @ScanLine;
    for J := 1 to 4 do
     begin
      InLineP(UnPackBits, @SrcPtr, @DstPtr, 72);
      if Ord(SrcPtr) > Ord(@Buffer) + 512 then
       begin
        Buffer[1] := Buffer[2];
        if not Eof(F) then
         Read(F, Buffer[2]);
        SrcPtr := Pointer(Ord(SrcPtr) - 512)
       end
     end;
    CopyBits(B, G^.Portbits, B.Bounds, DstRect, SrcCopy, MaskRgn);
    OffsetRect(DstRect, 0, 1);
   end
end.

[For anyone out there who doesn't have a copy of the aforementioned writeup
by Atkinson, I would be willing to explain this program in  greater detail
at a later date if any interest is expressed.]

   There is, unfortunately, a problem with I/O that makes life much worse
when you're doing disk I/O. Suppose you have a program that reads from a
disk file and writes to another file on the same disk (such as Stuart
Reges' program).

   On reading a character, say, MacPascal calls the File Manager to read
one byte from the file. The File Manager keeps a file buffer so that,
unless the buffer is empty or invalid, it needs only to extract the
requested byte from the buffer and return it. Unfortunately, MacPascal
always lets the File Manager use its default buffer, which is a single
512-byte buffer shared by all open files on the same volume. Therefore, when
a character is subsequently written out to another file on the same disk,
the buffer containing the character initially read is flushed to make room
for a write buffer for the output file. Likewise, upon reading the next
character, the write buffer must be flushed (i.e., written out) to make
room for a read buffer. And so on... The next release should no longer have
this problem.

   For now, there are a couple of strategies for getting around this
problem. First of all, if you are fortunate enough to have an external
drive, reading from a file on one drive and writing to a file on the other
will proceed more quickly (since they are on different volumes, they don't
share buffers).

   A second strategy is, as indicated above, to read and write in larger
chunks. Reading and writing a line at a time will perform significantly
better than a character at a time. You can even do your own buffering on a
block or multi-block basis, but don't expect to be able to declare a file
of blocks (i.e., a suitably large array-type) and read a text file a whole
block at a time - a text file is not likely to end exactly on a block
boundary. [Reading by blocks for MacPaint files works because they *always*
are a multiple of 512 bytes.]

   There is another common complaint about MacPascal I/O, and that is the
speed (or lack thereof) of writing to the Text window. I won't explain in
detail as to why this is so (a hint - the Text window is maintained with
TE!). I will only comment that there is, again, much more overhead required
to write character by character than if you write in larger chunks. Writing
out entire lines at a time goes much faster. If you really want to blast
text out to the screen, you can use WriteDraw to display text to the
Drawing window. The "Browser" program on the release disk contains some
sample code showing how to do this.

Concerning CHAR vs. STRING type compatibility:

   Let me respond to Stuart Reges' comments via some quotes from the
MacPascal reference manual:

[Section 1.6]
   "A character-string represents a value of a string-type. As a
string-type, a character-string is compatible not only with other
string-types, but also char-types..."

[Section 5.1.5.3]
   "...a char value is compatible with a string-type value, and when the
two are compared, the char value is treated as a string-type value with
length 1."

   I'll admit that the buggy if-statement presented by Reges is
problematical. However, if you've extended Pascal to allow *real* strings
(not just packed arrays of characters), then *not* allowing chars to be
compatible with multi-character string is *really* problematical (have you
ever tried to insert a CHAR into a STRING with UCSD Pascal or LisaPascal?).
I suggest, rather, that the real problem is the narrowness of the
proportionally-spaced space. Believe me, we are *seriously* considering
monospacing quoted strings in the next release.

     Jon F. Hueras, Think Technologies, Inc.
     (singer@harvard)