Algorithms and Data Structures
Algorithms and Data Structures
Algorithms and Data Structures
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
N.Wirth. <strong>Algorithms</strong> <strong>and</strong> <strong>Data</strong> <strong>Structures</strong>. Oberon version 29<br />
relatively large blocks once the tape is moving. Similar conditions hold for magnetic disks, where the data<br />
are allocated on tracks with a fixed number of blocks of fixed size, the so-called block size. In fact, a disk<br />
should be regarded as an array of blocks, each block being read or written as a whole, containing typically<br />
2 k bytes with k = 8, 9, … 12.<br />
Our programs, however, do not observe any such timing constraints. In order to allow them to ignore<br />
the constraints, the data to be transferred are buffered. They are collected in a buffer variable (in main<br />
store) <strong>and</strong> transferred when a sufficient amount of data is accumulated to form a block of the required size.<br />
The buffer's client has access only via the two procedures deposit <strong>and</strong> fetch:<br />
DEFINITION Buffer;<br />
PROCEDURE deposit (x: CHAR);<br />
PROCEDURE fetch (VAR x: CHAR);<br />
END Buffer.<br />
Buffering has an additional advantage in allowing the process which generates (receives) data to proceed<br />
concurrently with the device that writes (reads) the data from (to) the buffer. In fact, it is convenient to<br />
regard the device as a process itself which merely copies data streams. The buffer's purpose is to provide a<br />
certain degree of decoupling between the two processes, which we shall call the producer <strong>and</strong> the<br />
consumer. If, for example, the consumer is slow at a certain moment, it may catch up with the producer<br />
later on. This decoupling is often essential for a good utilization of peripheral devices, but it has only an<br />
effect, if the rates of producer <strong>and</strong> consumer are about the same on the average, but fluctuate at times. The<br />
degree of decoupling grows with increasing buffer size.<br />
We now turn to the question of how to represent a buffer, <strong>and</strong> shall for the time being assume that data<br />
elements are deposited <strong>and</strong> fetched individually instead of in blocks. A buffer essentially constitutes a firstin-first-out<br />
queue (fifo). If it is declared as an array, two index variables, say in <strong>and</strong> out, mark the positions<br />
of the next location to be written into <strong>and</strong> to be read from. Ideally, such an array should have no index<br />
bounds. A finite array is quite adequate, however, considering the fact that elements once fetched are no<br />
longer relevant. Their location may well be re-used. This leads to the idea of the circular buffer.<br />
in<br />
out<br />
Fig. 1.8. Circular buffer with indices in <strong>and</strong> out.<br />
The operations of depositing <strong>and</strong> fetching an element are expressed in the following module, which<br />
exports these operations as procedures, but hides the buffer <strong>and</strong> its index variables — <strong>and</strong> thereby<br />
effectively the buffering mechanism — from the client processes. This mechanism also involves a variable n<br />
counting the number of elements currently in the buffer. If N denotes the size of the buffer, the condition<br />
0 ≤ n ≤ N. Therefore, the operation fetch must be guarded by the condition n > 0 (buffer non-empty), <strong>and</strong><br />
the operation deposit by the condition n < N (buffer non-full). Not meeting the former condition must be<br />
regarded as a programming error, a violation of the latter as a failure of the suggested implementation<br />
(buffer too small).