Skip to content

Commit 64eb9ab

Browse files
committed
Auto merge of rust-lang#98324 - conradludgate:write-vectored-vec, r=Mark-Simulacrum
attempt to optimise vectored write benchmarked: old: ``` test io::cursor::tests::bench_write_vec ... bench: 68 ns/iter (+/- 2) test io::cursor::tests::bench_write_vec_vectored ... bench: 913 ns/iter (+/- 31) ``` new: ``` test io::cursor::tests::bench_write_vec ... bench: 64 ns/iter (+/- 0) test io::cursor::tests::bench_write_vec_vectored ... bench: 747 ns/iter (+/- 27) ``` More unsafe than I wanted (and less gains) in the end, but it still does the job
2 parents 5ffa8f6 + 803083a commit 64eb9ab

File tree

2 files changed

+149
-25
lines changed

2 files changed

+149
-25
lines changed

library/std/src/io/cursor.rs

+101-25
Original file line numberDiff line numberDiff line change
@@ -396,38 +396,99 @@ fn slice_write_vectored(
396396
Ok(nwritten)
397397
}
398398

399-
// Resizing write implementation
400-
fn vec_write<A>(pos_mut: &mut u64, vec: &mut Vec<u8, A>, buf: &[u8]) -> io::Result<usize>
401-
where
402-
A: Allocator,
403-
{
399+
/// Reserves the required space, and pads the vec with 0s if necessary.
400+
fn reserve_and_pad<A: Allocator>(
401+
pos_mut: &mut u64,
402+
vec: &mut Vec<u8, A>,
403+
buf_len: usize,
404+
) -> io::Result<usize> {
404405
let pos: usize = (*pos_mut).try_into().map_err(|_| {
405406
io::const_io_error!(
406407
ErrorKind::InvalidInput,
407408
"cursor position exceeds maximum possible vector length",
408409
)
409410
})?;
410-
// Make sure the internal buffer is as least as big as where we
411-
// currently are
412-
let len = vec.len();
413-
if len < pos {
414-
// use `resize` so that the zero filling is as efficient as possible
415-
vec.resize(pos, 0);
416-
}
417-
// Figure out what bytes will be used to overwrite what's currently
418-
// there (left), and what will be appended on the end (right)
419-
{
420-
let space = vec.len() - pos;
421-
let (left, right) = buf.split_at(cmp::min(space, buf.len()));
422-
vec[pos..pos + left.len()].copy_from_slice(left);
423-
vec.extend_from_slice(right);
411+
412+
// For safety reasons, we don't want these numbers to overflow
413+
// otherwise our allocation won't be enough
414+
let desired_cap = pos.saturating_add(buf_len);
415+
if desired_cap > vec.capacity() {
416+
// We want our vec's total capacity
417+
// to have room for (pos+buf_len) bytes. Reserve allocates
418+
// based on additional elements from the length, so we need to
419+
// reserve the difference
420+
vec.reserve(desired_cap - vec.len());
421+
}
422+
// Pad if pos is above the current len.
423+
if pos > vec.len() {
424+
let diff = pos - vec.len();
425+
// Unfortunately, `resize()` would suffice but the optimiser does not
426+
// realise the `reserve` it does can be eliminated. So we do it manually
427+
// to eliminate that extra branch
428+
let spare = vec.spare_capacity_mut();
429+
debug_assert!(spare.len() >= diff);
430+
// Safety: we have allocated enough capacity for this.
431+
// And we are only writing, not reading
432+
unsafe {
433+
spare.get_unchecked_mut(..diff).fill(core::mem::MaybeUninit::new(0));
434+
vec.set_len(pos);
435+
}
424436
}
425437

438+
Ok(pos)
439+
}
440+
441+
/// Writes the slice to the vec without allocating
442+
/// # Safety: vec must have buf.len() spare capacity
443+
unsafe fn vec_write_unchecked<A>(pos: usize, vec: &mut Vec<u8, A>, buf: &[u8]) -> usize
444+
where
445+
A: Allocator,
446+
{
447+
debug_assert!(vec.capacity() >= pos + buf.len());
448+
vec.as_mut_ptr().add(pos).copy_from(buf.as_ptr(), buf.len());
449+
pos + buf.len()
450+
}
451+
452+
/// Resizing write implementation for [`Cursor`]
453+
///
454+
/// Cursor is allowed to have a pre-allocated and initialised
455+
/// vector body, but with a position of 0. This means the [`Write`]
456+
/// will overwrite the contents of the vec.
457+
///
458+
/// This also allows for the vec body to be empty, but with a position of N.
459+
/// This means that [`Write`] will pad the vec with 0 initially,
460+
/// before writing anything from that point
461+
fn vec_write<A>(pos_mut: &mut u64, vec: &mut Vec<u8, A>, buf: &[u8]) -> io::Result<usize>
462+
where
463+
A: Allocator,
464+
{
465+
let buf_len = buf.len();
466+
let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?;
467+
468+
// Write the buf then progress the vec forward if necessary
469+
// Safety: we have ensured that the capacity is available
470+
// and that all bytes get written up to pos
471+
unsafe {
472+
pos = vec_write_unchecked(pos, vec, buf);
473+
if pos > vec.len() {
474+
vec.set_len(pos);
475+
}
476+
};
477+
426478
// Bump us forward
427-
*pos_mut = (pos + buf.len()) as u64;
428-
Ok(buf.len())
479+
*pos_mut += buf_len as u64;
480+
Ok(buf_len)
429481
}
430482

483+
/// Resizing write_vectored implementation for [`Cursor`]
484+
///
485+
/// Cursor is allowed to have a pre-allocated and initialised
486+
/// vector body, but with a position of 0. This means the [`Write`]
487+
/// will overwrite the contents of the vec.
488+
///
489+
/// This also allows for the vec body to be empty, but with a position of N.
490+
/// This means that [`Write`] will pad the vec with 0 initially,
491+
/// before writing anything from that point
431492
fn vec_write_vectored<A>(
432493
pos_mut: &mut u64,
433494
vec: &mut Vec<u8, A>,
@@ -436,11 +497,26 @@ fn vec_write_vectored<A>(
436497
where
437498
A: Allocator,
438499
{
439-
let mut nwritten = 0;
440-
for buf in bufs {
441-
nwritten += vec_write(pos_mut, vec, buf)?;
500+
// For safety reasons, we don't want this sum to overflow ever.
501+
// If this saturates, the reserve should panic to avoid any unsound writing.
502+
let buf_len = bufs.iter().fold(0usize, |a, b| a.saturating_add(b.len()));
503+
let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?;
504+
505+
// Write the buf then progress the vec forward if necessary
506+
// Safety: we have ensured that the capacity is available
507+
// and that all bytes get written up to the last pos
508+
unsafe {
509+
for buf in bufs {
510+
pos = vec_write_unchecked(pos, vec, buf);
511+
}
512+
if pos > vec.len() {
513+
vec.set_len(pos);
514+
}
442515
}
443-
Ok(nwritten)
516+
517+
// Bump us forward
518+
*pos_mut += buf_len as u64;
519+
Ok(buf_len)
444520
}
445521

446522
#[stable(feature = "rust1", since = "1.0.0")]

library/std/src/io/cursor/tests.rs

+48
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ fn test_vec_writer() {
2020
#[test]
2121
fn test_mem_writer() {
2222
let mut writer = Cursor::new(Vec::new());
23+
writer.set_position(10);
2324
assert_eq!(writer.write(&[0]).unwrap(), 1);
2425
assert_eq!(writer.write(&[1, 2, 3]).unwrap(), 3);
2526
assert_eq!(writer.write(&[4, 5, 6, 7]).unwrap(), 4);
@@ -30,6 +31,17 @@ fn test_mem_writer() {
3031
3
3132
);
3233
let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
34+
assert_eq!(&writer.get_ref()[..10], &[0; 10]);
35+
assert_eq!(&writer.get_ref()[10..], b);
36+
}
37+
38+
#[test]
39+
fn test_mem_writer_preallocated() {
40+
let mut writer = Cursor::new(vec![0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 10]);
41+
assert_eq!(writer.write(&[0]).unwrap(), 1);
42+
assert_eq!(writer.write(&[1, 2, 3]).unwrap(), 3);
43+
assert_eq!(writer.write(&[4, 5, 6, 7]).unwrap(), 4);
44+
let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
3345
assert_eq!(&writer.get_ref()[..], b);
3446
}
3547

@@ -517,3 +529,39 @@ fn const_cursor() {
517529
const _: &&[u8] = CURSOR.get_ref();
518530
const _: u64 = CURSOR.position();
519531
}
532+
533+
#[bench]
534+
fn bench_write_vec(b: &mut test::Bencher) {
535+
let slice = &[1; 128];
536+
537+
b.iter(|| {
538+
let mut buf = b"some random data to overwrite".to_vec();
539+
let mut cursor = Cursor::new(&mut buf);
540+
541+
let _ = cursor.write_all(slice);
542+
test::black_box(&cursor);
543+
})
544+
}
545+
546+
#[bench]
547+
fn bench_write_vec_vectored(b: &mut test::Bencher) {
548+
let slices = [
549+
IoSlice::new(&[1; 128]),
550+
IoSlice::new(&[2; 256]),
551+
IoSlice::new(&[3; 512]),
552+
IoSlice::new(&[4; 1024]),
553+
IoSlice::new(&[5; 2048]),
554+
IoSlice::new(&[6; 4096]),
555+
IoSlice::new(&[7; 8192]),
556+
IoSlice::new(&[8; 8192 * 2]),
557+
];
558+
559+
b.iter(|| {
560+
let mut buf = b"some random data to overwrite".to_vec();
561+
let mut cursor = Cursor::new(&mut buf);
562+
563+
let mut slices = slices;
564+
let _ = cursor.write_all_vectored(&mut slices);
565+
test::black_box(&cursor);
566+
})
567+
}

0 commit comments

Comments
 (0)