diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index fac694e..ef05f8e 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -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 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: 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**: diff --git a/Programs/CosmOS/Source/sbfs.asm b/Programs/CosmOS/Source/sbfs.asm index e0cc7b5..22f2d60 100644 --- a/Programs/CosmOS/Source/sbfs.asm +++ b/Programs/CosmOS/Source/sbfs.asm @@ -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: