Skip to content

Proposal for a growing owned segment's allocator #257

Open
@marmeladema

Description

@marmeladema

For similar reasons as explained in #243 (comment), I need an owned version of ScratchSpaceHeapAllocator.

Here is my proposal for a growing and owned segments allocator:

use capnp::message::{Allocator, HeapAllocator};
use capnp::{word, Word};

const BYTES_PER_WORD: usize = 8;
const ZERO: Word = word(0, 0, 0, 0, 0, 0, 0, 0);

/// An Allocator whose first segment is backed by a growing vector.
///
/// Recall that an `Allocator` implementation must ensure that allocated segments are
/// initially *zeroed*. `GrowingScratchSpaceHeapAllocator` ensures that is the case by
/// zeroing the entire buffer upon initial construction, and then zeroing any
/// *potentially used* part of the buffer upon `deallocate_segment()`.
///
/// You can reuse a `GrowingScratchSpaceHeapAllocator` by calling `message::Builder::into_allocator()`,
/// or by initally passing it to `message::Builder::new()` as a `&mut GrowingScratchSpaceHeapAllocator`.
/// Such reuse can save significant amounts of zeroing.
pub struct GrowingScratchSpaceHeapAllocator {
    scratch_space: Vec<Word>,
    scratch_space_allocated: bool,
    allocator: HeapAllocator,
    currently_allocated: u32,
    maximum_allocated: u32,
}

impl GrowingScratchSpaceHeapAllocator {
    pub fn with_capacity(capacity: usize) -> Self {
        // We need to ensure that the buffer is zeroed.
        let scratch_space = Word::allocate_zeroed_vec(capacity);

        Self {
            scratch_space,
            scratch_space_allocated: false,
            allocator: HeapAllocator::new(),
            currently_allocated: 0,
            maximum_allocated: 0,
        }
    }
}

impl Default for GrowingScratchSpaceHeapAllocator {
    fn default() -> Self {
        Self {
            scratch_space: Vec::new(),
            scratch_space_allocated: false,
            allocator: HeapAllocator::new(),
            currently_allocated: 0,
            maximum_allocated: 0,
        }
    }
}

unsafe impl<'a> Allocator for GrowingScratchSpaceHeapAllocator {
    fn allocate_segment(&mut self, minimum_size: u32) -> (*mut u8, u32) {
        let (ptr, len) = if (minimum_size as usize) < self.scratch_space.len()
            && !self.scratch_space_allocated
        {
            self.scratch_space_allocated = true;
            (
                Word::words_to_bytes_mut(&mut self.scratch_space).as_mut_ptr(),
                self.scratch_space.len() as u32,
            )
        } else {
            self.allocator.allocate_segment(minimum_size)
        };

        self.currently_allocated += len;
        if self.currently_allocated > self.maximum_allocated {
            self.maximum_allocated = self.currently_allocated;
        }

        (ptr, len)
    }

    fn deallocate_segment(&mut self, ptr: *mut u8, word_size: u32, words_used: u32) {
        self.currently_allocated -= word_size;
        if ptr == Word::words_to_bytes_mut(&mut self.scratch_space).as_mut_ptr() {
            // Rezero the slice to allow reuse of the allocator. We only need to write
            // words that we know might contain nonzero values.
            unsafe {
                core::ptr::write_bytes(ptr, 0u8, (words_used as usize) * BYTES_PER_WORD);
            }
            self.scratch_space_allocated = false;
            let maximum_allocated = self.maximum_allocated as usize;
            if maximum_allocated > self.scratch_space.len() {
                self.scratch_space.resize(maximum_allocated, ZERO);
            }
        } else {
            self.allocator
                .deallocate_segment(ptr, word_size, words_used);
        }
    }
}

This is mostly a verbatim copy of ScratchSpaceHeapAllocator but which uses an internal Vec<Word> as first segment instead of a user provided mutable slice.

I am sharing this to get some feedback and why not merge it upstream if deemed appropriate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions