[comp.os.cpm] Help on caching algorythm?

w_smith@wookie.DEC.COM.UUCP (03/08/87)

     I'm rebuilding my BIOS to (among other things) change my 
hard disk track buffers over to caching.  I've gotten almost the 
whole thing written, but I have one algorythm I need some help 
on.  I have 4 (will be 8) buffers in another bank of memory, 
which each holds an 8 K logical track.  Each access to a buffer 
increments a counter, so I know how often (since last warm boot) 
a buffer has been used.  I've done the easy part, finding a 
buffer (if I have a cache 'hit') or allocating an unused buffer, 
now I'm looking for an algorythm to deallocate a buffer when I 
don't have a 'hit' and there aren't any free.  I have a few 
ideas, but there are problems with each:
     LRU: Least Recently Used.  I think the algorythm goes 
something like "when you use a buffer, move it's number to the 
top of a block of memory, shuffle others down to make room, and 
if you need a buffer, use the one whose number is at the bottom 
of the list."  The problems are maintaining the data structure 
without reams of code, and the fact that a buffer with a high use 
count can be deallocated by merely reading 4 (8) different tracks 
before you get back to the first one.  Thus you could lose your 
directory track fairly rapidly, and I know I'm going to need that 
frequently.
     Smallest count.  Look for the buffer that got used the 
least, and use that.  Looks good as an idea, but there is a major 
coding problem.  Given a table of 4 (8) numbers, how do you find 
the smallest? [I'm coding in Z-80 assembly, so MIN(x,y,z) doesn't 
help :+)  ].  If you start scanning from the beginning, you will 
tend to deallocate number 0 more than number 3.  If you don't 
start at the beginning, the coding gets messy.  If there are 2 
numbers that are a minimum, which one do you pick?  I though of a 
way that would work, "start with one, look for it in the table, 
if not found, look for two, if not found, look for 3, etc, etc, 
etc".  I'm using byte counters, so I'd only have to go up to 255, 
but this is kind of a hack, any better ideas?
     Are there any other, more popular, more useful, simpler, or 
more elegancache algorythms that anyone knows about?
 
Willie Smith
w_smith@wookie.dec.com
w_smith%wookie.dec.com@decwrl.dec.com [decwrl.arpa?]
 
Usenet:	the_known_universe!decwrl!wookie.dec.com!w_smith
BIX:	w_smith

coln@hpl-opus.UUCP (03/11/87)

Regarding CPM disk caching:

I, too, have recently added disk caching to my BIOS code to improve effective
hard disk performance, with good results.

However, I cache on a disk record (128 byte) basis, reducing the granularity
problem.  Since the BDOS requests records in any case, this puts the cache
logically between the BDOS and any blocking/deblocking/track-reading code that
you might have in the physical disk driver.

I have 180 kbytes of bank switched memory which is divided between the hash
table (2048 words), the cached records themselves (128 bytes/record), and the
table identifying the cached records (4 bytes/entry).  Each cached record is
identified by its drive designator, track, and sector (as seen by the BDOS).
The cache is currently operating with "write through", so the disk is never
out of sync.  Preliminary profiling with a typical mix (for me) of editing,
assembling, and file manipulation operations suggests that record writes are
only 5%-10% of the total accesses.  Reading a large file from the cache
(assuming no cache misses but including all the operating system overhead for
directory lookup) runs under 2 milliseconds per record.  The hashed record
lookup is crucial to achieve this speed.

To reallocate cache space I use a simple "not recently used" algorithm,
somewhat similar to what are called "clock" algorithms.  Each record in the
cache is assigned a flag bit, set whenever the record is accessed.  Upon a
cache miss, a reallocation pointer circulates through the cache entries,
looking for an entry with the flag not set, and unsetting the flag of each
entry it passes.  Note that it is guaranteed to eventually find an unset flag.
Records that are used often, will set their bit again, before they are dropped
from the cache on the next circulation of the pointer.

The code is (naturally?) written in Z-80 assembly, and is fairly clean, but
has machine dependencies due to the memory bank switching.  Please let me know
if you desire further information, or can make suggestions.

Mike Coln
hplabs!coln
coln@hplabs.HP.COM

barr@convex.UUCP (03/12/87)

Take a look at Maurice J. Bach's book "The Design of the UNIX
Operating System."  Look specifically at the section describing
the chapter describing the algorithms and data structures used
to implement the buffer cache.  (I think it's in chapter 3.)
The key idea is that the linked list structures automatically
implement an LRU strategy which is a lot easier and faster than
maintaining and checking reference history.