block: use larger segment size for cache prefetch

This commit is contained in:
Mark Poliakov 2024-12-05 21:05:41 +02:00
parent f79cae5368
commit 3a5f9b6ced
2 changed files with 98 additions and 43 deletions

View File

@ -249,7 +249,9 @@ impl Ext2Fs {
let mapper = match cached {
false => DeviceMapper::uncached(device, block_size),
true => DeviceMapper::cached_with_capacity(device, block_size, 512),
true => {
DeviceMapper::cached_with_capacity(device, block_size, block_size * 16, 128, 64)
}
};
Ok(Self {

View File

@ -14,7 +14,7 @@ use crate::task::sync::AsyncMutex;
use super::BlockDevice;
pub struct CachedBlock<
pub struct CachedSegment<
A: PhysicalMemoryAllocator<Address = PhysicalAddress> = GlobalPhysicalAllocator,
> {
data: PageBox<[u8], A>,
@ -41,16 +41,25 @@ pub struct BlockCache<
> {
device: &'static dyn BlockDevice,
block_size: usize,
cache: AsyncMutex<LruCache<u64, Arc<IrqSafeRwLock<CachedBlock<A>>>>>,
segment_size: usize,
cache: AsyncMutex<LruCache<u64, Arc<IrqSafeRwLock<CachedSegment<A>>>>>,
}
impl DeviceMapper {
pub fn cached_with_capacity(
device: &'static dyn BlockDevice,
block_size: usize,
segment_size: usize,
bucket_capacity: usize,
bucket_count: usize,
) -> DeviceMapper {
DeviceMapper::cached_with_capacity_in(device, block_size, bucket_capacity)
DeviceMapper::cached_with_capacity_in(
device,
block_size,
segment_size,
bucket_capacity,
bucket_count,
)
}
pub fn uncached(device: &'static dyn BlockDevice, block_size: usize) -> DeviceMapper {
@ -62,9 +71,17 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DeviceMapper<A> {
pub fn cached_with_capacity_in(
device: &'static dyn BlockDevice,
block_size: usize,
segment_size: usize,
bucket_capacity: usize,
bucket_count: usize,
) -> DeviceMapper<A> {
let cache = BlockCache::<A>::with_capacity_in(device, block_size, bucket_capacity);
let cache = BlockCache::<A>::with_capacity_in(
device,
block_size,
segment_size,
bucket_capacity,
bucket_count,
);
DeviceMapper::<A>::Cached(cache)
}
@ -159,15 +176,36 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> BlockCache<A> {
pub fn with_capacity_in(
device: &'static dyn BlockDevice,
block_size: usize,
segment_size: usize,
bucket_capacity: usize,
bucket_count: usize,
) -> BlockCache<A> {
if block_size % device.block_size() != 0 {
panic!("Cache block size is not multiple of device block size");
}
assert_eq!(
block_size % device.block_size(),
0,
"Block size is not a multiple of device block size"
);
assert_eq!(
segment_size % block_size,
0,
"Segment size is not a multiple of block size"
);
assert!(
segment_size >= block_size,
"Segment size is less than block size"
);
log::info!("New block cache: block: {block_size}B, segment: {segment_size}B, geometry: {bucket_capacity}x{bucket_count}");
log::info!(
"Worst-case memory usage: {}K",
(segment_size * bucket_capacity * bucket_count) / 1024
);
BlockCache {
device,
block_size,
cache: AsyncMutex::new(LruCache::with_capacity(bucket_capacity, 8)),
segment_size,
cache: AsyncMutex::new(LruCache::with_capacity(bucket_capacity, bucket_count)),
}
}
@ -175,28 +213,38 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> BlockCache<A> {
self.device
}
async fn evict_block(&self, pos: u64, block: Arc<IrqSafeRwLock<CachedBlock<A>>>) {
async fn evict_block(
&self,
segment_position: u64,
block: Arc<IrqSafeRwLock<CachedSegment<A>>>,
) {
let read = block.read();
if read.dirty {
if let Err(err) = self.device.write_aligned(pos, read.data.as_slice()).await {
log::error!("Disk error: flushing block {}: {:?}", pos, err);
assert_eq!(segment_position % self.segment_size as u64, 0);
if let Err(err) = self
.device
.write_aligned(segment_position, read.data.as_slice())
.await
{
log::error!("Disk error: flushing block {}: {:?}", segment_position, err);
}
}
}
async fn fetch_block(
&self,
pos: u64,
write_size: Option<usize>,
) -> Result<Arc<IrqSafeRwLock<CachedBlock<A>>>, Error> {
let need_read = write_size.map(|sz| sz != self.block_size).unwrap_or(true);
let mut data = PageBox::new_uninit_slice_in(self.block_size)?;
// Don't read a block that's going to be fully rewritten immediately
if need_read {
self.device.read_aligned(pos, data.as_slice_mut()).await?;
}
segment_position: u64,
) -> Result<Arc<IrqSafeRwLock<CachedSegment<A>>>, Error> {
// let need_read = write_size.map(|sz| sz != self.block_size).unwrap_or(true);
let mut data = PageBox::new_uninit_slice_in(self.segment_size)?;
// // Don't read a block that's going to be fully rewritten immediately
// if need_read {
self.device
.read_aligned(segment_position, data.as_slice_mut())
.await?;
// }
let data = unsafe { data.assume_init_slice() };
Ok(Arc::new(IrqSafeRwLock::new(CachedBlock {
Ok(Arc::new(IrqSafeRwLock::new(CachedSegment {
data,
dirty: false,
})))
@ -204,16 +252,16 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> BlockCache<A> {
async fn entry(
&self,
pos: u64,
write_size: Option<usize>,
) -> Result<Arc<IrqSafeRwLock<CachedBlock<A>>>, Error> {
segment_position: u64,
) -> Result<Arc<IrqSafeRwLock<CachedSegment<A>>>, Error> {
assert_eq!(segment_position % self.segment_size as u64, 0);
let mut lock = self.cache.lock().await;
let (value, evicted) = lock
.try_get_or_insert_with_async(pos, || self.fetch_block(pos, write_size))
.try_get_or_insert_with_async(segment_position, || self.fetch_block(segment_position))
.await?;
if let Some((pos, block)) = evicted {
self.evict_block(pos, block).await;
if let Some((segment_position, block)) = evicted {
self.evict_block(segment_position, block).await;
}
Ok(value.clone())
@ -221,41 +269,46 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> BlockCache<A> {
pub async fn try_with<T, F: FnOnce(&[u8]) -> Result<T, Error>>(
&self,
pos: u64,
block_position: u64,
mapper: F,
) -> Result<T, Error> {
let block = self.entry(pos, None).await?;
let result = mapper(&block.read()[..])?;
let segment_position = block_position & !(self.segment_size as u64 - 1);
let segment_offset = (block_position & (self.segment_size as u64 - 1)) as usize;
let block = self.entry(segment_position).await?;
let result = mapper(&block.read()[segment_offset..segment_offset + self.block_size])?;
Ok(result)
}
pub async fn try_with_mut<T, F: FnOnce(&mut [u8]) -> Result<T, Error>>(
&self,
pos: u64,
block_position: u64,
size: usize,
mapper: F,
) -> Result<T, Error> {
let block = self.entry(pos, Some(size)).await?;
let segment_position = block_position & !(self.segment_size as u64 - 1);
let segment_offset = (block_position & (self.segment_size as u64 - 1)) as usize;
let block = self.entry(segment_position).await?;
let mut block = block.write();
let result = mapper(&mut block[..])?;
block.dirty = true;
let result = mapper(&mut block[segment_offset..segment_offset + self.block_size])?;
Ok(result)
}
pub async fn flush(&self) {
for (pos, block) in self.cache.lock().await.flush() {
self.evict_block(pos, block).await;
}
todo!()
// for (pos, block) in self.cache.lock().await.flush() {
// self.evict_block(pos, block).await;
// }
}
}
impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> CachedBlock<A> {
impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> CachedSegment<A> {
pub fn set_dirty(&mut self) {
self.dirty = true;
}
}
impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> Deref for CachedBlock<A> {
impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> Deref for CachedSegment<A> {
type Target = PageBox<[u8], A>;
fn deref(&self) -> &Self::Target {
@ -263,7 +316,7 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> Deref for CachedBloc
}
}
impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DerefMut for CachedBlock<A> {
impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DerefMut for CachedSegment<A> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.dirty = true;
&mut self.data
@ -407,7 +460,7 @@ mod tests {
async fn test_no_modification() {
let device = Box::leak(Box::new(DummyBlock::new(BS, 1024)));
let wrapper = NgBlockDeviceWrapper::new(device);
let cache = BlockCache::<PA>::with_capacity_in(wrapper, BS, 64);
let cache = BlockCache::<PA>::with_capacity_in(wrapper, BS, BS, 64, 8);
device.deny_writes.store(true, Ordering::Release);
cache
@ -433,7 +486,7 @@ mod tests {
let device = Box::leak(Box::new(DummyBlock::new(BS, 1024)));
let wrapper = NgBlockDeviceWrapper::new(device);
// 8 * 8
let cache = BlockCache::<PA>::with_capacity_in(wrapper, BS, 8);
let cache = BlockCache::<PA>::with_capacity_in(wrapper, BS, BS, 8, 8);
const LBA: u64 = 1;
cache
@ -513,7 +566,7 @@ mod tests {
let device = Box::leak(Box::new(DummyBlock::new(BS, 1024)));
let wrapper = NgBlockDeviceWrapper::new(device);
// 8 * 8
let cache = BlockCache::<PA>::with_capacity_in(wrapper, BS, 8);
let cache = BlockCache::<PA>::with_capacity_in(wrapper, BS, BS, 8, 8);
fn mapper(x: u64) -> u8 {
(x + 3) as u8