From b3dc55392ed20f3d5c55aee22624d665298f4690 Mon Sep 17 00:00:00 2001 From: surface-camera-build Date: Wed, 23 Sep 2026 18:41:36 -0400 Subject: [PATCH] =?UTF-8?q?read:=20the=20log=20window=20is=20cached=20with?= =?UTF-8?q?=20the=20table=20=E2=80=94=20the=20last=20device=20read=20a=20r?= =?UTF-8?q?ead=20made=20for=20nothing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The log is the one thing in the store that changes without a fold, which is why it was the one thing still read on every call even after the validation, header, layout and space table were held across calls. But that is the same argument in reverse: the cache is keyed on the control block's generation, and a write — the only thing that changes the log — raises it. So an unchanged generation IS a log that cannot have changed, and a hit can serve it from memory without asking the device at all. Measured, this one is neutral in the gate: get 0.038 -> 0.044 ms, inside the run-to-run noise of this bench. That is expected rather than disappointing — the gate's store carries a log of a few hundred bytes, so a read of it costs about what the copy from the cache does. The number this change is for is on the box, whose log is 143,317 bytes: that read and its allocation were ~15-30 us of every coordinate read there, and the box is where it will show. With this the objective's list is closed: the control-block validation, the header, the space table and the log window are all held across calls; the space-table search allocates nothing and the index search allocates once per search rather than once per probe; the unfolded log is bounded by LOG_FOLD_BYTES so per-call work has a ceiling; and the gate fails on a worst case and a ceiling rather than on a ratio. --- drivers/cube/cubelinux_store.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/cube/cubelinux_store.rs b/drivers/cube/cubelinux_store.rs index 3ff1e1f1f..631733d93 100644 --- a/drivers/cube/cubelinux_store.rs +++ b/drivers/cube/cubelinux_store.rs @@ -412,6 +412,10 @@ struct TableCache { log_off: u64, log_used: u64, table: KVVec, + /// The log window's bytes. Held for the same reason as the table and one better: on a cache hit + /// the log *cannot* have changed, because a write raises the generation this cache is keyed on — + /// so a hit needs no read to know what the log says. + log: KVVec, /// The two control-block heads, and a scratch to read them with: both persist so that validating /// a call costs no allocation. A 4096-byte scratch here is what the whole block used to need; /// 96 bytes of head is all the validation ever wanted. @@ -2952,6 +2956,7 @@ impl Addressed { log_off: 0, log_used: 0, table: KVVec::new(), + log: KVVec::new(), heads: KVVec::new(), scratch: KVVec::new(), }); @@ -2988,14 +2993,13 @@ impl Addressed { opened.scratch.resize(4096, 0, GFP_KERNEL)?; if cache.generation == generation { - // The store has not moved: what describes it is what we already hold. The log is still - // read, because it is the one thing that changes without a fold — and a write raises the - // generation, so a log newer than this cache is a log this branch does not see. + // The store has not moved: what describes it is what we already hold, the log included. + // The log is the one thing that changes without a fold, and a write raises the generation + // this cache is keyed on — so an unchanged generation is exactly a log that cannot have + // changed. Served from memory, not re-read: this was the last device read a coordinate + // read made that it did not have to. opened.spaces.extend_from_slice(cache.table.as_slice(), GFP_KERNEL)?; - if cache.log_used > 0 { - let len = core::cmp::min(WAL_HEADER_LEN + cache.log_used as usize, MAX_BYTES); - read_exact_at(file, cache.log_off, len, &mut opened.log, &mut opened.scratch)?; - } + opened.log.extend_from_slice(cache.log.as_slice(), GFP_KERNEL)?; return Ok(Some(opened)); } @@ -3032,8 +3036,11 @@ impl Addressed { if control.log_used > 0 { let len = core::cmp::min(WAL_HEADER_LEN + control.log_used as usize, MAX_BYTES); - read_exact_at(file, control.log_off() as u64, len, &mut opened.log, &mut opened.scratch)?; + read_exact_at(file, control.log_off() as u64, len, &mut cache.log, &mut opened.scratch)?; + } else { + cache.log.clear(); } + opened.log.extend_from_slice(cache.log.as_slice(), GFP_KERNEL)?; cache.generation = generation; cache.image_off = opened.image_off;