Compare commits

...
Author SHA1 Message Date
Ludovic MarieandClaude Opus 5.5 0b15350d2a Random ticks: read ahead through LevelAccess::prefetch_block
The random-tick-sections probe records the blocks the ticks read as the drawn
positions; the read-ahead goes through its own LevelAccess method, which the
game leaves as a plain read and the probe leaves out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 17:17:37 +02:00
Ludovic MarieandClaude Opus 5.5 4328f9fd70 Random ticks: read a section's drawn blocks together before ticking them
The block reads of random ticks miss the cache and ran one after the other,
each behind the previous tick. Nothing a random tick does draws from
randValue, so a section's positions are drawn up to four at a time and their
blocks read together first, letting the misses overlap; each tick then reads
its block again from the cache. Same positions, same order, same ticks.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 16:28:16 +02:00
3 changed files with 28 additions and 3 deletions

No files matched your search

@@ -30,6 +30,8 @@ impl LevelAccess for Air {
self.reads.borrow_mut().push(pos);
BlockStateId(0)
}
// The reads the ticks themselves make are the drawn positions.
fn prefetch_block(&self, _: BlockPos) {}
fn set_raw(&mut self, _: BlockPos, _: BlockStateId) -> Option<BlockStateId> {
None
}
+7
View File
@@ -290,6 +290,13 @@ pub enum LevelEvent {
pub trait LevelAccess {
/// `getBlockState`. Unloaded or out-of-bounds positions read as air.
fn block_state(&self, pos: BlockPos) -> BlockStateId;
/// Read a block ahead of `block_state` asking for it, so the memory it
/// lives in is cached by then (random ticks read a few at once to overlap
/// their cache misses). No effect on the game: a test level that records
/// reads can leave it out.
fn prefetch_block(&self, pos: BlockPos) {
std::hint::black_box(self.block_state(pos));
}
/// Storage write (`LevelChunkSection.setBlockState`). Returns the previous
/// state, or None when nothing could be stored (unloaded chunk).
fn set_raw(&mut self, pos: BlockPos, state: BlockStateId) -> Option<BlockStateId>;
+19 -3
View File
@@ -1644,9 +1644,25 @@ impl Behaviours {
}
let mut from = level.min_y();
while let Some(min_y) = next_ticking(level, from).filter(|&y| y < level.max_y()) {
for _ in 0..speed {
let pos = ticks.block_random_pos(min_x, min_y, min_z, 15);
self.sim(level, ticks).random_tick_at(pos);
// Nothing a random tick does draws from randValue, so a section's
// positions are drawn a few at a time and their blocks read
// together first: those reads miss the cache, and independent
// misses overlap. Each tick then reads its block again (cached),
// as the ticks before it may have changed it.
let mut left = speed;
while left > 0 {
let n = left.min(4) as usize;
left -= n as i32;
let mut drawn = [BlockPos::new(0, 0, 0); 4];
for pos in &mut drawn[..n] {
*pos = ticks.block_random_pos(min_x, min_y, min_z, 15);
}
for pos in &drawn[..n] {
level.prefetch_block(*pos);
}
for pos in &drawn[..n] {
self.sim(level, ticks).random_tick_at(*pos);
}
}
from = min_y + 16;
}