Read the next block while the program is busy with this one
A file is read front to back, so when something asks for a block, the one after it is almost certainly wanted next. sbfsReadOne asks the disk for it straight away and hands back the block that was wanted - so the transfer happens while the caller is busy, and the waiting is mostly over by the time it comes back. Printing a fourteen kilobyte file: 1,064,498 cycles at two thousand a block becomes 976,882, and 1,576,562 at ten thousand becomes 1,032,889. The second figure barely moves between those - seven per cent from an instant disk to a slow one, where without it the same change costs sixty eight. A machine that reads ahead stops caring very much how fast its disk is. NOT FOR DIRECTORY SEARCHES, and that was tried first and thrown away. A scan stops the moment it matches, so the block it would read ahead is one nobody will ever look at - a transfer to fetch and another wait to throw away. It was nineteen per cent SLOWER on a lookup at ten thousand cycles a block. Reading ahead is a bet that the next block is wanted, and a search is exactly the case that hopes it is not. The scan loop is untouched. Three per cent is what it costs when the disk is instant, which is the bookkeeping with nothing to hide behind it, and the default. The disk has one buffer, so the only way to know what is in it is to remember what was last asked for. Every read records that; a write clears it, because a write fills the buffer from memory and no read asked for what is in it. Getting that wrong would blit whatever happened to be there and call it the block somebody wanted. The read ahead is bounded by the file's own length, so reading the last block does not fetch whatever follows the file on the disk - which belongs to somebody else and would be paid for twice, once to fetch and once to discard. It cost one bug, and an instructive one: the next index was worked out by stepping SbfsIndex, which is the CALLER'S and is still wanted after the return - handleFileBlock compares it against the file's length to see whether this was the short last block. Every block reported the wrong number of bytes, and the output of Type skipped five lines in the middle. It has a place of its own now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
d4cba36c5e
commit
db3d349da8
@@ -701,6 +701,35 @@ end of the file - files are contiguous, so block nine of a three block file is a
|
|||||||
belonging to something else, and writing it would put one file's bytes inside another with
|
belonging to something else, and writing it would put one file's bytes inside another with
|
||||||
nothing anywhere saying so.
|
nothing anywhere saying so.
|
||||||
|
|
||||||
|
### Reading Ahead:
|
||||||
|
|
||||||
|
A file is read front to back, so when a program asks for a block, the one after it is
|
||||||
|
almost certainly wanted next. `sbfsReadOne` asks the disk for it straight away and hands
|
||||||
|
the caller the block it wanted - so the transfer happens while the program is busy with
|
||||||
|
what it already has, and the wait is mostly gone by the time it comes back.
|
||||||
|
|
||||||
|
Nothing is done differently and nothing is done out of order. The machine simply stops
|
||||||
|
standing still.
|
||||||
|
|
||||||
|
**It is not done for directory searches, and that is not an oversight.** A scan stops the
|
||||||
|
moment it matches, so the next block is one nobody will ever look at: it costs a transfer to
|
||||||
|
fetch and another wait to throw away. Tried there, it was nineteen per cent *slower*. Read
|
||||||
|
ahead is a bet that the next block is wanted, and a search is exactly the case that hopes it
|
||||||
|
is not.
|
||||||
|
|
||||||
|
What it is worth, printing a fourteen kilobyte file:
|
||||||
|
|
||||||
|
| Cycles a block | Without | With |
|
||||||
|
| -- | -- | -- |
|
||||||
|
| 0 | 936,626 | 962,959 |
|
||||||
|
| 2,000 | 1,064,498 | 976,882 |
|
||||||
|
| 10,000 | 1,576,562 | 1,032,889 |
|
||||||
|
|
||||||
|
The second column barely moves. From an instant disk to a slow one the cost rises seven per
|
||||||
|
cent, where without it the same change costs sixty eight - which is the point: **a machine
|
||||||
|
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.
|
||||||
|
|
||||||
### Saving Something Twice:
|
### 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**:
|
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**:
|
||||||
|
|||||||
@@ -1224,13 +1224,63 @@ sbfsReadOne:
|
|||||||
SETD.2 SbfsIndex
|
SETD.2 SbfsIndex
|
||||||
CALL sbfsAddWord
|
CALL sbfsAddWord
|
||||||
|
|
||||||
|
; ---- READ AHEAD ----
|
||||||
|
;
|
||||||
|
; Is this block already coming? It will be if the last time through asked for the one
|
||||||
|
; before it, which is what reading a file front to back does every time. Then there is
|
||||||
|
; nothing to start: the disk has been fetching it while the caller was busy with the last
|
||||||
|
; one, and all that is left is to wait for it.
|
||||||
|
;
|
||||||
|
; This is the path a search is NOT. A directory scan stops the moment it matches, so
|
||||||
|
; asking for the next block there is asking for one nobody will look at - and it was
|
||||||
|
; nineteen per cent slower for exactly that reason. A file is read to its end, so every
|
||||||
|
; block asked for early is one that would have been asked for anyway.
|
||||||
|
SETD.0 SbfsBufferKnown
|
||||||
|
LDA.0
|
||||||
|
BRA sbfsReadOneFresh
|
||||||
|
SETD.0 SbfsBufferBlock
|
||||||
|
SETD.2 SbfsBlock
|
||||||
|
CALL sbfsCompareWord
|
||||||
|
BNQ sbfsReadOneFresh
|
||||||
|
|
||||||
|
RCAL sbfsWaitDisk ; Already on its way, so only the waiting is left.
|
||||||
|
INIB 0x02
|
||||||
|
AND
|
||||||
|
BNQ sbfsReadOneDone
|
||||||
|
BRI sbfsReadOneHere
|
||||||
|
|
||||||
|
sbfsReadOneFresh:
|
||||||
CALL sbfsReadBlock
|
CALL sbfsReadBlock
|
||||||
BNQ sbfsReadOneDone ; The read failed, and Q says so.
|
BNQ sbfsReadOneDone ; The read failed, and Q says so.
|
||||||
|
|
||||||
|
sbfsReadOneHere:
|
||||||
PSHD.3
|
PSHD.3
|
||||||
POPD.1
|
POPD.1
|
||||||
CALL sbfsBufferOut
|
CALL sbfsBufferOut
|
||||||
|
|
||||||
|
; And the one after it, started now, if the file has one. Bounded by the file's own
|
||||||
|
; length so that reading the last block does not fetch whatever follows the file on the
|
||||||
|
; disk - which belongs to somebody else and would be paid for twice: once to fetch and
|
||||||
|
; once to throw away.
|
||||||
|
; IN A PLACE OF ITS OWN, not in SbfsIndex. What block was asked for is the caller's, and
|
||||||
|
; it is still wanted after this returns - handleFileBlock compares it against the file's
|
||||||
|
; length to work out whether this was the short last block. Stepping it here made every
|
||||||
|
; block report the wrong number of bytes.
|
||||||
|
CALL sbfsFileExtent
|
||||||
|
SETD.0 SbfsAheadIndex
|
||||||
|
SETD.2 SbfsIndex
|
||||||
|
CALL sbfsSetWord
|
||||||
|
SETD.0 SbfsAheadIndex
|
||||||
|
CALL sbfsStepWord
|
||||||
|
SETD.0 SbfsAheadIndex
|
||||||
|
SETD.2 SbfsWantBlocks
|
||||||
|
CALL sbfsCompareWord
|
||||||
|
BNC sbfsReadOneLast ; The next one is past the end of the file.
|
||||||
|
SETD.0 SbfsBlock
|
||||||
|
CALL sbfsStepWord
|
||||||
|
RCAL sbfsStartRead
|
||||||
|
sbfsReadOneLast:
|
||||||
|
|
||||||
RSTA
|
RSTA
|
||||||
RSTB
|
RSTB
|
||||||
CCF
|
CCF
|
||||||
@@ -1719,6 +1769,8 @@ sbfsBufferIn:
|
|||||||
|
|
||||||
; Puts the disk's buffer down as the block named by SbfsBlock. Q is zero if it worked.
|
; Puts the disk's buffer down as the block named by SbfsBlock. Q is zero if it worked.
|
||||||
sbfsWriteBlock:
|
sbfsWriteBlock:
|
||||||
|
RCAL sbfsWaitDisk ; Anything still going finishes before this asks for more.
|
||||||
|
RCAL sbfsForgetBuffer
|
||||||
SETD.0 SbfsBlock
|
SETD.0 SbfsBlock
|
||||||
LDA.0
|
LDA.0
|
||||||
OUTA 0x20
|
OUTA 0x20
|
||||||
@@ -1734,6 +1786,32 @@ sbfsWriteBlock:
|
|||||||
|
|
||||||
; Reads the block named by SbfsBlock into the disk's buffer. Q is zero if it worked.
|
; Reads the block named by SbfsBlock into the disk's buffer. Q is zero if it worked.
|
||||||
sbfsReadBlock:
|
sbfsReadBlock:
|
||||||
|
RCAL sbfsStartRead
|
||||||
|
RCAL sbfsWaitDisk
|
||||||
|
INIB 0x02
|
||||||
|
AND ; Q is the error bit, so zero means it worked.
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- Asking for a block without waiting for it ----
|
||||||
|
;
|
||||||
|
; The half of a read that costs nothing, and the whole of what makes reading ahead
|
||||||
|
; possible. Whatever the disk was still doing is collected first, because a controller
|
||||||
|
; given a command while it is busy has no good answer - so this is the one place that
|
||||||
|
; guarantees it is not, and every read goes through it.
|
||||||
|
;
|
||||||
|
; WHICH BLOCK IS COMING IS WRITTEN DOWN, because the disk has one buffer and the only way
|
||||||
|
; to know what is in it is to remember what was last asked for. Anything that fills the
|
||||||
|
; buffer some other way must say so by clearing it, or a later read would blit whatever
|
||||||
|
; happened to be there and call it the block it wanted.
|
||||||
|
sbfsStartRead:
|
||||||
|
RCAL sbfsWaitDisk
|
||||||
|
SETD.0 SbfsBlock
|
||||||
|
SETD.1 SbfsBufferBlock
|
||||||
|
CALL sbfsCopyWord
|
||||||
|
INIA 0x01
|
||||||
|
SETD.0 SbfsBufferKnown
|
||||||
|
STA.0
|
||||||
|
|
||||||
SETD.0 SbfsBlock
|
SETD.0 SbfsBlock
|
||||||
LDA.0
|
LDA.0
|
||||||
OUTA 0x20
|
OUTA 0x20
|
||||||
@@ -1742,10 +1820,14 @@ sbfsReadBlock:
|
|||||||
OUTA 0x21
|
OUTA 0x21
|
||||||
INIA 0x01
|
INIA 0x01
|
||||||
OUTA 0x22
|
OUTA 0x22
|
||||||
RCAL sbfsWaitDisk
|
RRET
|
||||||
INIB 0x02
|
|
||||||
AND ; Q is the error bit, so zero means it worked.
|
; The buffer is about to hold something this cannot describe.
|
||||||
RET
|
sbfsForgetBuffer:
|
||||||
|
RSTA
|
||||||
|
SETD.0 SbfsBufferKnown
|
||||||
|
STA.0
|
||||||
|
RRET
|
||||||
|
|
||||||
; ---- Waiting for the disk ----
|
; ---- Waiting for the disk ----
|
||||||
;
|
;
|
||||||
@@ -2877,6 +2959,14 @@ SbfsAt:
|
|||||||
0x00 0x00
|
0x00 0x00
|
||||||
SbfsScanIndex:
|
SbfsScanIndex:
|
||||||
0x00 0x00
|
0x00 0x00
|
||||||
|
|
||||||
|
; ---- What the disk's one buffer holds, or is about to ----
|
||||||
|
SbfsBufferBlock:
|
||||||
|
0x00 0x00
|
||||||
|
SbfsBufferKnown:
|
||||||
|
0x00
|
||||||
|
SbfsAheadIndex:
|
||||||
|
0x00 0x00
|
||||||
SbfsTarget:
|
SbfsTarget:
|
||||||
0x00 0x00
|
0x00 0x00
|
||||||
SbfsUpParent:
|
SbfsUpParent:
|
||||||
|
|||||||
Reference in New Issue
Block a user