Skip to content

Commit d6cfe19

Browse files
committed
allow persistent bounce buffers in MaybeBounce
This is particularly useful for virtio devices, where on-demand allocation of bounce buffers leads to sever performance impacts (~80%) in synthetic throughput tests. Additionally, for virtio devices we can know approximately what the optimal size of a statically allocated bounce buffer is. Allocate bounce buffers on the heap, as trying to even temporarily place a 65k bounce buffer on the stack can lead to stack overflow errors. Signed-off-by: Patrick Roy <[email protected]>
1 parent 8384b8f commit d6cfe19

File tree

3 files changed

+124
-30
lines changed

3 files changed

+124
-30
lines changed

src/vmm/src/builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ pub fn build_microvm_for_boot(
274274
}
275275

276276
let entry_point = load_kernel(
277-
MaybeBounce(&boot_config.kernel_file, secret_free),
277+
MaybeBounce::new(&boot_config.kernel_file, secret_free),
278278
vmm.vm.guest_memory(),
279279
)?;
280280
let initrd = match &boot_config.initrd_file {
@@ -286,7 +286,7 @@ pub fn build_microvm_for_boot(
286286

287287
Some(InitrdConfig::from_reader(
288288
vmm.vm.guest_memory(),
289-
MaybeBounce(initrd_file, secret_free),
289+
MaybeBounce::new(initrd_file, secret_free),
290290
u64_to_usize(size),
291291
)?)
292292
}

src/vmm/src/vstate/memory.rs

Lines changed: 121 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -63,55 +63,134 @@ pub enum MemoryError {
6363
/// Newtype that implements [`ReadVolatile`] and [`WriteVolatile`] if `T` implements `Read` or
6464
/// `Write` respectively, by reading/writing using a bounce buffer, and memcpy-ing into the
6565
/// [`VolatileSlice`].
66+
///
67+
/// Bounce buffers are allocated on the heap, as on-stack bounce buffers could cause stack
68+
/// overflows. If `N == 0` then bounce buffers will be allocated on demand.
6669
#[derive(Debug)]
67-
pub struct MaybeBounce<T>(pub T, pub bool);
70+
pub struct MaybeBounce<T, const N: usize = 0> {
71+
pub(crate) target: T,
72+
persistent_buffer: Option<Box<[u8; N]>>,
73+
}
74+
75+
impl<T> MaybeBounce<T, 0> {
76+
/// Creates a new `MaybeBounce` that always allocates a bounce
77+
/// buffer on-demand
78+
pub fn new(target: T, should_bounce: bool) -> Self {
79+
MaybeBounce::new_persistent(target, should_bounce)
80+
}
81+
}
82+
83+
impl<T, const N: usize> MaybeBounce<T, N> {
84+
/// Creates a new `MaybeBounce` that uses a persistent, fixed size bounce buffer
85+
/// of size `N`. If a read/write request exceeds the size of this bounce buffer, it
86+
/// is split into multiple, `<= N`-size read/writes.
87+
pub fn new_persistent(target: T, should_bounce: bool) -> Self {
88+
let mut bounce = MaybeBounce {
89+
target,
90+
persistent_buffer: None,
91+
};
92+
93+
if should_bounce {
94+
bounce.activate()
95+
}
96+
97+
bounce
98+
}
99+
100+
/// Activates this [`MaybeBounce`] to start doing reads/writes via a bounce buffer,
101+
/// which is allocated on the heap by this function (e.g. if `activate()` is never called,
102+
/// no bounce buffer is ever allocated).
103+
pub fn activate(&mut self) {
104+
self.persistent_buffer = Some(vec![0u8; N].into_boxed_slice().try_into().unwrap())
105+
}
106+
}
68107

69108
// FIXME: replace AsFd with ReadVolatile once &File: ReadVolatile in vm-memory.
70-
impl<T: Read + AsFd> ReadVolatile for MaybeBounce<T> {
109+
impl<T: Read + AsFd, const N: usize> ReadVolatile for MaybeBounce<T, N> {
71110
fn read_volatile<B: BitmapSlice>(
72111
&mut self,
73112
buf: &mut VolatileSlice<B>,
74113
) -> Result<usize, VolatileMemoryError> {
75-
if self.1 {
76-
let mut bbuf = vec![0; buf.len()];
77-
let n = self
78-
.0
79-
.read(bbuf.as_mut_slice())
80-
.map_err(VolatileMemoryError::IOError)?;
81-
buf.copy_from(&bbuf[..n]);
82-
Ok(n)
114+
if let Some(ref mut persistent) = self.persistent_buffer {
115+
let mut bbuf = (N == 0).then(|| vec![0u8; buf.len()]);
116+
let bbuf = bbuf.as_deref_mut().unwrap_or(persistent.as_mut_slice());
117+
118+
let mut buf = buf.offset(0)?;
119+
let mut total = 0;
120+
while !buf.is_empty() {
121+
let how_much = buf.len().min(bbuf.len());
122+
let n = self
123+
.target
124+
.read(&mut bbuf[..how_much])
125+
.map_err(VolatileMemoryError::IOError)?;
126+
buf.copy_from(&bbuf[..n]);
127+
128+
buf = buf.offset(n)?;
129+
total += n;
130+
131+
if n < how_much {
132+
break;
133+
}
134+
}
135+
136+
Ok(total)
83137
} else {
84-
self.0.as_fd().read_volatile(buf)
138+
self.target.as_fd().read_volatile(buf)
85139
}
86140
}
87141
}
88142

89-
impl<T: Write + AsFd> WriteVolatile for MaybeBounce<T> {
143+
impl<T: Write + AsFd, const N: usize> WriteVolatile for MaybeBounce<T, N> {
90144
fn write_volatile<B: BitmapSlice>(
91145
&mut self,
92146
buf: &VolatileSlice<B>,
93147
) -> Result<usize, VolatileMemoryError> {
94-
if self.1 {
95-
let mut bbuf = vec![0; buf.len()];
96-
buf.copy_to(bbuf.as_mut_slice());
97-
self.0
98-
.write(bbuf.as_slice())
99-
.map_err(VolatileMemoryError::IOError)
148+
if let Some(ref mut persistent) = self.persistent_buffer {
149+
let mut bbuf = (N == 0).then(|| vec![0u8; buf.len()]);
150+
let bbuf = bbuf.as_deref_mut().unwrap_or(persistent.as_mut_slice());
151+
152+
let mut buf = buf.offset(0)?;
153+
let mut total = 0;
154+
while !buf.is_empty() {
155+
let how_much = buf.copy_to(bbuf);
156+
let n = self
157+
.target
158+
.write(&bbuf[..how_much])
159+
.map_err(VolatileMemoryError::IOError)?;
160+
buf = buf.offset(n)?;
161+
total += n;
162+
163+
if n < how_much {
164+
break;
165+
}
166+
}
167+
168+
Ok(total)
100169
} else {
101-
self.0.as_fd().write_volatile(buf)
170+
self.target.as_fd().write_volatile(buf)
102171
}
103172
}
104173
}
105174

106-
impl<R: Read> Read for MaybeBounce<R> {
175+
impl<R: Read, const N: usize> Read for MaybeBounce<R, N> {
107176
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
108-
self.0.read(buf)
177+
self.target.read(buf)
178+
}
179+
}
180+
181+
impl<W: Write, const N: usize> Write for MaybeBounce<W, N> {
182+
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
183+
self.target.write(buf)
184+
}
185+
186+
fn flush(&mut self) -> std::io::Result<()> {
187+
self.target.flush()
109188
}
110189
}
111190

112-
impl<S: Seek> Seek for MaybeBounce<S> {
191+
impl<S: Seek, const N: usize> Seek for MaybeBounce<S, N> {
113192
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
114-
self.0.seek(pos)
193+
self.target.seek(pos)
115194
}
116195
}
117196

@@ -947,30 +1026,45 @@ mod tests {
9471026
fn test_bounce() {
9481027
let file_direct = TempFile::new().unwrap();
9491028
let file_bounced = TempFile::new().unwrap();
1029+
let file_persistent_bounced = TempFile::new().unwrap();
9501030

9511031
let mut data = (0..=255).collect_vec();
9521032

953-
MaybeBounce(file_direct.as_file(), false)
1033+
MaybeBounce::new(file_direct.as_file(), false)
1034+
.write_all_volatile(&VolatileSlice::from(data.as_mut_slice()))
1035+
.unwrap();
1036+
MaybeBounce::new(file_bounced.as_file(), true)
9541037
.write_all_volatile(&VolatileSlice::from(data.as_mut_slice()))
9551038
.unwrap();
956-
MaybeBounce(file_bounced.as_file(), true)
1039+
MaybeBounce::<_, 7>::new_persistent(file_persistent_bounced.as_file(), true)
9571040
.write_all_volatile(&VolatileSlice::from(data.as_mut_slice()))
9581041
.unwrap();
9591042

9601043
let mut data_direct = vec![0u8; 256];
9611044
let mut data_bounced = vec![0u8; 256];
1045+
let mut data_persistent_bounced = vec![0u8; 256];
9621046

9631047
file_direct.as_file().seek(SeekFrom::Start(0)).unwrap();
9641048
file_bounced.as_file().seek(SeekFrom::Start(0)).unwrap();
1049+
file_persistent_bounced
1050+
.as_file()
1051+
.seek(SeekFrom::Start(0))
1052+
.unwrap();
9651053

966-
MaybeBounce(file_direct.as_file(), false)
1054+
MaybeBounce::new(file_direct.as_file(), false)
9671055
.read_exact_volatile(&mut VolatileSlice::from(data_direct.as_mut_slice()))
9681056
.unwrap();
969-
MaybeBounce(file_bounced.as_file(), true)
1057+
MaybeBounce::new(file_bounced.as_file(), true)
9701058
.read_exact_volatile(&mut VolatileSlice::from(data_bounced.as_mut_slice()))
9711059
.unwrap();
1060+
MaybeBounce::<_, 7>::new_persistent(file_persistent_bounced.as_file(), true)
1061+
.read_exact_volatile(&mut VolatileSlice::from(
1062+
data_persistent_bounced.as_mut_slice(),
1063+
))
1064+
.unwrap();
9721065

9731066
assert_eq!(data_direct, data_bounced);
9741067
assert_eq!(data_direct, data);
1068+
assert_eq!(data_persistent_bounced, data);
9751069
}
9761070
}

src/vmm/src/vstate/vm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ impl Vm {
466466
.iter()
467467
.any(|r| r.inner().guest_memfd != 0);
468468
self.guest_memory()
469-
.dump(&mut MaybeBounce(&file, secret_hidden))
469+
.dump(&mut MaybeBounce::new(&file, secret_hidden))
470470
.and_then(|_| self.swiotlb_regions().dump(&mut file))?;
471471
self.reset_dirty_bitmap();
472472
self.guest_memory().reset_dirty();

0 commit comments

Comments
 (0)