Skip to content

Commit

Permalink
Allow to create Rc<[u8]> from a pseudo-Vec without a copy
Browse files Browse the repository at this point in the history
This compensates for the performance regression from f20b5b3.
  • Loading branch information
glandium committed Nov 3, 2023
1 parent 2e011a6 commit e458f88
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 10 deletions.
16 changes: 13 additions & 3 deletions src/libcinnabar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_void};

use crate::git::GitObjectId;
Expand Down Expand Up @@ -43,9 +44,18 @@ pub struct strslice_mut<'a> {
marker: PhantomData<&'a mut [u8]>,
}

impl<'a, T: AsMut<[u8]> + 'a> From<T> for strslice_mut<'a> {
fn from(mut buf: T) -> Self {
let buf = buf.as_mut();
impl<'a> From<&'a mut [u8]> for strslice_mut<'a> {
fn from(buf: &'a mut [u8]) -> Self {
strslice_mut {
len: buf.len(),
buf: buf.as_mut_ptr() as *mut c_char,
marker: PhantomData,
}
}
}

impl<'a> From<&'a mut [MaybeUninit<u8>]> for strslice_mut<'a> {
fn from(buf: &'a mut [MaybeUninit<u8>]) -> Self {
strslice_mut {
len: buf.len(),
buf: buf.as_mut_ptr() as *mut c_char,
Expand Down
17 changes: 10 additions & 7 deletions src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ use crate::libgit::{
use crate::oid::ObjectId;
use crate::progress::{progress_enabled, Progress};
use crate::tree_util::{diff_by_path, Empty, ParseTree, RecurseTree};
use crate::util::{FromBytes, ImmutBString, OsStrExt, ReadExt, SliceExt, ToBoxed, Transpose};
use crate::util::{
FromBytes, ImmutBString, OsStrExt, RcExt, ReadExt, SliceExt, ToBoxed, Transpose,
};
use crate::xdiff::{apply, textdiff, PatchInfo};
use crate::{check_enabled, do_reload, Checks};

Expand Down Expand Up @@ -574,7 +576,7 @@ impl RawHgManifest {
let last_manifest = cache.take();
let tree_id = oid.get_tree_id();

let mut manifest = Vec::new();
let mut manifest = Rc::<[u8]>::builder();
if let Some(last_manifest) = last_manifest {
let reference_manifest = last_manifest.content.clone();
if last_manifest.tree_id == tree_id {
Expand Down Expand Up @@ -639,7 +641,7 @@ impl RawHgManifest {
RawHgManifest::write_one_entry(&entry, &mut manifest).unwrap();
}
}
let content = Rc::<[u8]>::from(manifest.as_ref());
let content = manifest.into_rc();

cache.set(Some(ManifestCache {
tree_id,
Expand Down Expand Up @@ -1426,20 +1428,21 @@ pub fn store_changegroup<R: Read>(input: R, version: u8) {
}
mn_size += reference_mn.len() - last_end;

let mut stored_manifest = vec![0; mn_size];
let mut stored_manifest = Rc::builder_with_capacity(mn_size);
unsafe {
store_manifest(
&manifest,
(&reference_mn).into(),
(&mut stored_manifest).into(),
(&mut stored_manifest.spare_capacity_mut()[..mn_size]).into(),
);
stored_manifest.set_len(mn_size);
}
let stored_manifest = Rc::<[u8]>::from(stored_manifest.as_ref());

let tree_id = mid.to_git().unwrap().get_tree_id();
MANIFESTCACHE.with(|cache| {
cache.set(Some(ManifestCache {
tree_id,
content: stored_manifest,
content: stored_manifest.into_rc(),
}));
});
}
Expand Down
195 changes: 195 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::alloc::Layout;
use std::cell::Cell;
use std::cmp;
use std::ffi::{CStr, CString, OsStr};
use std::io::{self, LineWriter, Read, Write};
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::{Deref, DerefMut};
#[cfg(unix)]
use std::os::unix::ffi;
#[cfg(windows)]
use std::os::windows::ffi;
use std::ptr::{self, NonNull};
use std::rc::Rc;
use std::str::{self, FromStr};
use std::{fmt, mem};

Expand Down Expand Up @@ -432,3 +440,190 @@ pub trait Transpose {

fn transpose(self) -> Self::Target;
}

type RcBox = [Cell<usize>; 2];

pub struct RcSliceBuilder<T> {
ptr: NonNull<T>,
len: usize,
capacity: usize,
marker: PhantomData<T>,
}

impl<T> RcSliceBuilder<T> {
pub fn new() -> Self {
RcSliceBuilder {
ptr: NonNull::dangling(),
len: 0,
capacity: 0,
marker: PhantomData,
}
}

pub fn with_capacity(capacity: usize) -> Self {
let mut result = Self::new();
if capacity > 0 {
result.grow_to(capacity);
}
result
}

pub fn into_rc(self) -> Rc<[T]> {
if self.len != 0 {
let (layout, offset) = Self::layout_for_size(self.len);
let ptr = if layout.size() != self.capacity {
let (current_layout, _) = Self::layout_for_size(self.capacity);
// We need to shrink to fit so that Rc's deallocation layout matches ours.
unsafe {
let ptr = std::alloc::realloc(
self.ptr.cast::<u8>().as_ptr().sub(offset),
current_layout,
layout.size(),
);
if ptr.is_null() {
panic!("Out of memory");
}
NonNull::new_unchecked(ptr.add(offset) as *mut T)
}
} else {
self.ptr
};
let len = self.len;
mem::forget(self);
unsafe {
ptr::write(
ptr.cast::<u8>().as_ptr().sub(offset) as *mut RcBox,
[Cell::new(1), Cell::new(1)],
);
Rc::from_raw(NonNull::slice_from_raw_parts(ptr, len).as_ptr())
}
} else {
Rc::new([])
}
}

fn layout_for_size(size: usize) -> (Layout, usize) {
Layout::array::<T>(size)
.and_then(|layout| Layout::new::<RcBox>().extend(layout))
.map(|(layout, offset)| (layout.pad_to_align(), offset))
.unwrap()
}

#[inline(never)]
fn grow_to(&mut self, needed_len: usize) {
// Same growth strategy as git's ALLOC_GROW.
let new_capacity = cmp::max(
needed_len,
self.capacity
.checked_add(16)
.and_then(|cap| cap.checked_mul(3))
.map_or(needed_len, |cap| cap / 2),
);
let (layout, offset) = Self::layout_for_size(new_capacity);
unsafe {
let ptr = if self.capacity == 0 {
std::alloc::alloc(layout)
} else {
let (current_layout, _) = Self::layout_for_size(self.capacity);
std::alloc::realloc(
self.ptr.cast::<u8>().as_ptr().sub(offset),
current_layout,
layout.size(),
)
};
if ptr.is_null() {
panic!("Out of memory");
}
self.ptr = NonNull::new_unchecked(ptr.add(offset) as *mut T);
self.capacity = layout.size() - offset;
}
}

#[inline(always)]
pub fn reserve(&mut self, additional: usize) {
let new_len = self.len.checked_add(additional).unwrap();
if new_len > self.capacity {
self.grow_to(new_len);
}
}
}

impl<T: Copy> RcSliceBuilder<T> {
pub fn extend_from_slice(&mut self, other: &[T]) {
self.reserve(other.len());
unsafe {
ptr::copy_nonoverlapping(other.as_ptr(), self.ptr.as_ptr().add(self.len), other.len());
}
self.len += other.len();
}

pub fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit<T>] {
unsafe {
std::slice::from_raw_parts_mut(
self.ptr.as_ptr().add(self.len) as *mut MaybeUninit<T>,
self.capacity - self.len,
)
}
}

pub unsafe fn set_len(&mut self, new_len: usize) {
debug_assert!(new_len <= self.capacity);
self.len = new_len;
}
}

impl<T> Deref for RcSliceBuilder<T> {
type Target = [T];

fn deref(&self) -> &Self::Target {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
}

impl<T> DerefMut for RcSliceBuilder<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_mut(), self.len) }
}
}

impl<T> Drop for RcSliceBuilder<T> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(NonNull::slice_from_raw_parts(self.ptr, self.len).as_ptr());
if self.capacity > 0 {
let (layout, offset) = Self::layout_for_size(self.capacity);
std::alloc::dealloc(self.ptr.cast::<u8>().as_ptr().sub(offset), layout);
}
}
}
}

impl Write for RcSliceBuilder<u8> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.extend_from_slice(buf);
Ok(buf.len())
}

fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

pub trait RcExt {
type Builder;

fn builder() -> Self::Builder;
fn builder_with_capacity(capacity: usize) -> Self::Builder;
}

impl<T> RcExt for Rc<[T]> {
type Builder = RcSliceBuilder<T>;

fn builder() -> Self::Builder {
Self::Builder::new()
}

fn builder_with_capacity(capacity: usize) -> Self::Builder {
Self::Builder::with_capacity(capacity)
}
}

0 comments on commit e458f88

Please sign in to comment.