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
@@ -1224,13 +1224,63 @@ sbfsReadOne:
|
||||
SETD.2 SbfsIndex
|
||||
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
|
||||
BNQ sbfsReadOneDone ; The read failed, and Q says so.
|
||||
|
||||
sbfsReadOneHere:
|
||||
PSHD.3
|
||||
POPD.1
|
||||
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
|
||||
RSTB
|
||||
CCF
|
||||
@@ -1719,6 +1769,8 @@ sbfsBufferIn:
|
||||
|
||||
; Puts the disk's buffer down as the block named by SbfsBlock. Q is zero if it worked.
|
||||
sbfsWriteBlock:
|
||||
RCAL sbfsWaitDisk ; Anything still going finishes before this asks for more.
|
||||
RCAL sbfsForgetBuffer
|
||||
SETD.0 SbfsBlock
|
||||
LDA.0
|
||||
OUTA 0x20
|
||||
@@ -1734,6 +1786,32 @@ sbfsWriteBlock:
|
||||
|
||||
; Reads the block named by SbfsBlock into the disk's buffer. Q is zero if it worked.
|
||||
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
|
||||
LDA.0
|
||||
OUTA 0x20
|
||||
@@ -1742,10 +1820,14 @@ sbfsReadBlock:
|
||||
OUTA 0x21
|
||||
INIA 0x01
|
||||
OUTA 0x22
|
||||
RCAL sbfsWaitDisk
|
||||
INIB 0x02
|
||||
AND ; Q is the error bit, so zero means it worked.
|
||||
RET
|
||||
RRET
|
||||
|
||||
; The buffer is about to hold something this cannot describe.
|
||||
sbfsForgetBuffer:
|
||||
RSTA
|
||||
SETD.0 SbfsBufferKnown
|
||||
STA.0
|
||||
RRET
|
||||
|
||||
; ---- Waiting for the disk ----
|
||||
;
|
||||
@@ -2877,6 +2959,14 @@ SbfsAt:
|
||||
0x00 0x00
|
||||
SbfsScanIndex:
|
||||
0x00 0x00
|
||||
|
||||
; ---- What the disk's one buffer holds, or is about to ----
|
||||
SbfsBufferBlock:
|
||||
0x00 0x00
|
||||
SbfsBufferKnown:
|
||||
0x00
|
||||
SbfsAheadIndex:
|
||||
0x00 0x00
|
||||
SbfsTarget:
|
||||
0x00 0x00
|
||||
SbfsUpParent:
|
||||
|
||||
Reference in New Issue
Block a user