mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: qcow: Refactor pointer table writes to use iterators
Refactor write_pointer_table to accept iterators instead of requiring materialized vectors, eliminating temporary allocations in L1 table sync operations. Changes: - Modified write_pointer_table() to take Iterator<Item = &T> and dereference internally before passing owned values to the callback - Added write_pointer_table_direct() convenience wrapper for cases without value transformation - Updated sync_caches() to use l1_table.iter() directly instead of .get_values().iter().copied() - Implemented Deref<Target = [T]> for VecCache to enable direct .iter() Performance impact: - Eliminates L1 table allocation during sync (~2KB per 100GB disk) - L2 and refcount table writes already used slices, no change there - Zero performance overhead: iterator dereferencing is equivalent to .copied() and optimizes identically The L1 sync previously collected entries into a Vec to apply the OFLAG_COPIED flag. The new iterator+callback pattern computes this on-the-fly, avoiding the allocation entirely. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
@@ -773,7 +773,7 @@ impl QcowFile {
|
||||
let raw_file = &mut self.raw_file;
|
||||
self.l2_cache
|
||||
.insert(l1_index, table, |index, evicted| {
|
||||
raw_file.write_pointer_table(l1_table[index], evicted.get_values(), 0)
|
||||
raw_file.write_pointer_table_direct(l1_table[index], evicted.iter())
|
||||
})
|
||||
.map_err(Error::EvictingCache)?;
|
||||
}
|
||||
@@ -998,7 +998,7 @@ impl QcowFile {
|
||||
|
||||
// Rewrite the top-level refcount table.
|
||||
raw_file
|
||||
.write_pointer_table(header.refcount_table_offset, ref_table, 0)
|
||||
.write_pointer_table_direct(header.refcount_table_offset, ref_table.iter())
|
||||
.map_err(Error::WritingHeader)?;
|
||||
|
||||
// Rewrite the header again, now with lazy refcounts disabled.
|
||||
@@ -1513,7 +1513,7 @@ impl QcowFile {
|
||||
let l1_table = &self.l1_table;
|
||||
let raw_file = &mut self.raw_file;
|
||||
self.l2_cache.insert(l1_index, l2_table, |index, evicted| {
|
||||
raw_file.write_pointer_table(l1_table[index], evicted.get_values(), 0)
|
||||
raw_file.write_pointer_table_direct(l1_table[index], evicted.iter())
|
||||
})?;
|
||||
}
|
||||
Ok(new_cluster)
|
||||
@@ -1596,7 +1596,7 @@ impl QcowFile {
|
||||
let addr = self.l1_table[*l1_index];
|
||||
if addr != 0 {
|
||||
self.raw_file
|
||||
.write_pointer_table(addr, l2_table.get_values(), 0)?;
|
||||
.write_pointer_table_direct(addr, l2_table.iter())?;
|
||||
} else {
|
||||
return Err(std::io::Error::from_raw_os_error(EINVAL));
|
||||
}
|
||||
@@ -1610,25 +1610,22 @@ impl QcowFile {
|
||||
// Push L1 table and refcount table last as all the clusters they point to are now
|
||||
// guaranteed to be valid.
|
||||
let mut sync_required = if self.l1_table.dirty() {
|
||||
// Build L1 table with OFLAG_COPIED bits set correctly based on L2 cluster refcounts
|
||||
let l1_active: Vec<u64> = self
|
||||
.l1_table
|
||||
.get_values()
|
||||
.iter()
|
||||
.map(|&l2_addr| {
|
||||
// Write L1 table with OFLAG_COPIED bits
|
||||
let refcounts = &mut self.refcounts;
|
||||
self.raw_file.write_pointer_table(
|
||||
self.header.l1_table_offset,
|
||||
self.l1_table.iter(),
|
||||
|raw_file, l2_addr| {
|
||||
if l2_addr == 0 {
|
||||
Ok(0)
|
||||
} else {
|
||||
let refcount = self
|
||||
.refcounts
|
||||
.get_cluster_refcount(&mut self.raw_file, l2_addr)
|
||||
let refcount = refcounts
|
||||
.get_cluster_refcount(raw_file, l2_addr)
|
||||
.map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?;
|
||||
Ok(l1_entry_make(l2_addr, refcount == 1))
|
||||
}
|
||||
})
|
||||
.collect::<io::Result<Vec<u64>>>()?;
|
||||
self.raw_file
|
||||
.write_pointer_table(self.header.l1_table_offset, &l1_active, 0)?;
|
||||
},
|
||||
)?;
|
||||
self.l1_table.mark_clean();
|
||||
true
|
||||
} else {
|
||||
|
||||
@@ -61,24 +61,47 @@ impl QcowRawFile {
|
||||
self.read_pointer_table(offset, count, mask)
|
||||
}
|
||||
|
||||
/// Writes `table` of u64 pointers to `offset` in the file.
|
||||
/// `non_zero_flags` will be ORed with all non-zero values in `table`.
|
||||
/// writing.
|
||||
pub fn write_pointer_table(
|
||||
/// Internal helper for creating a buffered writer for pointer tables.
|
||||
#[inline]
|
||||
fn setup_pointer_table_writer<T>(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
table: &[u64],
|
||||
non_zero_flags: u64,
|
||||
) -> io::Result<()> {
|
||||
entries: &impl Iterator<Item = T>,
|
||||
) -> io::Result<BufWriter<RawFile>> {
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file);
|
||||
for addr in table {
|
||||
let val = if *addr == 0 {
|
||||
0
|
||||
} else {
|
||||
*addr | non_zero_flags
|
||||
};
|
||||
buffer.write_u64::<BigEndian>(val)?;
|
||||
let my_file = self.file.try_clone()?;
|
||||
let capacity = entries.size_hint().0 * size_of::<u64>();
|
||||
Ok(BufWriter::with_capacity(capacity, my_file))
|
||||
}
|
||||
|
||||
/// Writes a pointer table to `offset` in the file.
|
||||
/// Entries are computed on-the-fly by the callback.
|
||||
pub fn write_pointer_table<'a, T: Copy + 'a>(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
entries: impl Iterator<Item = &'a T>,
|
||||
mut f: impl FnMut(&mut QcowRawFile, T) -> io::Result<u64>,
|
||||
) -> io::Result<()> {
|
||||
let mut buffer = self.setup_pointer_table_writer(offset, &entries)?;
|
||||
|
||||
for addr in entries {
|
||||
let entry = f(self, *addr)?;
|
||||
buffer.write_u64::<BigEndian>(entry)?;
|
||||
}
|
||||
buffer.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a pointer table directly without transforming values.
|
||||
pub fn write_pointer_table_direct<'a>(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
entries: impl Iterator<Item = &'a u64>,
|
||||
) -> io::Result<()> {
|
||||
let mut buffer = self.setup_pointer_table_writer(offset, &entries)?;
|
||||
|
||||
for &entry in entries {
|
||||
buffer.write_u64::<BigEndian>(entry)?;
|
||||
}
|
||||
buffer.flush()?;
|
||||
Ok(())
|
||||
|
||||
@@ -156,11 +156,8 @@ impl RefCount {
|
||||
/// Returns true if the table changed since the previous `flush_table()` call.
|
||||
pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
|
||||
if self.ref_table.dirty() {
|
||||
raw_file.write_pointer_table(
|
||||
self.refcount_table_offset,
|
||||
self.ref_table.get_values(),
|
||||
0,
|
||||
)?;
|
||||
raw_file
|
||||
.write_pointer_table_direct(self.refcount_table_offset, self.ref_table.iter())?;
|
||||
self.ref_table.mark_clean();
|
||||
Ok(true)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user