The filesystem waits for the disk instead of asking it repeatedly
sbfsWaitDisk spun on the status port. Read ahead had already hidden about
three quarters of the latency, and what remained was still 11.5% of Type
over a 14K file on a ten thousand cycle disk - all of it memory traffic
spent finding out that nothing had happened yet.
It tests the port first and only waits if the disk is still busy, and that
order is the whole of what makes it safe: the disk raises its line when it
finishes, so a disk that finished in the gap between the test and the WAIT
has its line standing already and the WAIT does nothing rather than
sleeping through the answer. No handler and no vector - the shell keeps the
Interrupt Flag down, and a WAIT wakes on a line whether or not anybody
means to answer it.
Printing a 14K file, by where the cycles go:
cycles a block total bus waiting
0 922,570 922,570 0
2,000 946,474 922,702 23,772
10,000 1,042,474 922,702 119,772
The bus column stops moving. What the program costs in memory is now the
same whatever the disk does. On this emulator nothing observable changes;
on hardware it is a CPU standing out of the way of the memory controller
rather than competing with it for every one of those 119,772 cycles.
The first version cost 660 cycles more at latency zero because it read the
status port again on the way out. AND writes to Q and leaves A alone, so
the status was already there - which is what the original said in its own
comment, and what I stopped believing while rewriting around it.
This commit is contained in:
@@ -776,6 +776,33 @@ cent, where without it the same change costs sixty eight - which is the point: *
|
||||
that reads ahead stops caring very much how fast its disk is.** The three per cent it costs
|
||||
at zero is the bookkeeping, paid when there is nothing to hide behind it.
|
||||
|
||||
### And Waiting For What Is Left:
|
||||
|
||||
Read ahead hides most of the wait and cannot hide all of it. What remained was a loop
|
||||
asking the disk's status port over and over, which is work the machine is doing and memory
|
||||
it is touching to find out that nothing has happened yet.
|
||||
|
||||
`sbfsWaitDisk` uses `WAIT` now. It tests the status port, and only if the disk is still
|
||||
busy does it stop - the CPU is put down until a device raises a line, and the disk raises
|
||||
one when it finishes. **Asking first is what makes it safe:** if the disk finished in the
|
||||
gap between the test and the `WAIT`, its line is already standing and the `WAIT` does
|
||||
nothing rather than sleeping through the answer.
|
||||
|
||||
There is no handler and no vector. The shell keeps the Interrupt Flag down, and a `WAIT`
|
||||
wakes on a line whether or not anybody intends to answer it, taking it down on the way
|
||||
past. Printing the same fourteen kilobyte file:
|
||||
|
||||
| Cycles a block | Total | Of that, the bus | Waiting |
|
||||
| -- | -- | -- | -- |
|
||||
| 0 | 922,570 | 922,570 | 0 |
|
||||
| 2,000 | 946,474 | 922,702 | 23,772 |
|
||||
| 10,000 | 1,042,474 | 922,702 | 119,772 |
|
||||
|
||||
**The middle column stops moving.** What the program costs in memory is now the same
|
||||
whatever the disk does, and the difference is time spent with the bus quiet. On this
|
||||
emulator that changes nothing anybody can see; on hardware it is the difference between a
|
||||
CPU contending for memory with everything else and a CPU standing out of the way.
|
||||
|
||||
### Saving Something Twice:
|
||||
|
||||
Which is why saving a document is not the same as writing a file, and why sbfsSaveFile exists rather than each tool doing it. A file that has grown will usually not fit where it was, so saving it means putting it somewhere else and letting go of where it was - and **the obvious order is a trap**:
|
||||
|
||||
@@ -1892,12 +1892,23 @@ sbfsForgetBuffer:
|
||||
; settled status, and CALL would put A back the way it found it - so the one thing this
|
||||
; exists to hand over is the one thing an ordinary call cannot carry. Two bytes of Stack
|
||||
; instead of ten, as well, in a routine that runs on every block the machine ever touches.
|
||||
; ASKED FIRST, THEN WAITED ON, and that order is the whole of what makes this safe. The
|
||||
; disk raises its line when it finishes, so if it finished between the test and the WAIT
|
||||
; the line is already standing and the WAIT does nothing rather than sleeping through the
|
||||
; answer. Testing after waiting would have exactly the opposite property.
|
||||
;
|
||||
; No handler and no vector: the shell keeps the Interrupt Flag down, and a WAIT wakes on a
|
||||
; line whether or not anybody is going to answer it. It takes the line down on the way
|
||||
; past, which is what stops the next wait finding this one's line and returning at once.
|
||||
sbfsWaitDisk:
|
||||
INA 0x23
|
||||
INIB 0x01
|
||||
AND
|
||||
BNQ sbfsWaitDisk ; The busy bit is up, so round again.
|
||||
RRET ; A is the status, with the busy bit down.
|
||||
BRQ sbfsWaitDone ; The busy bit is down, so there is nothing to wait for.
|
||||
WAIT
|
||||
BRI sbfsWaitDisk
|
||||
sbfsWaitDone:
|
||||
RRET ; A is the status, with the busy bit down: AND leaves A alone.
|
||||
|
||||
; Copies the disk's buffer, a whole block of it, to Data Memory at DP1.
|
||||
sbfsBufferOut:
|
||||
|
||||
Reference in New Issue
Block a user