Skip to content

Commit

Permalink
pw_allocator: Add size reports for blocks
Browse files Browse the repository at this point in the history
This CL adds a separate size report for DetailedBlock, which is
currently the only provided block implementation. Subsequent CLs will
add additional block implementations and size reports. Separating the
rerports allows users to see how much code size is due to block choice
and how much is due to allocator choice.

Change-Id: I2ca1ac6d19302bd5fdbefc372e42d44dde1bcc38
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/264637
Reviewed-by: Alexei Frolov <[email protected]>
Lint: Lint 🤖 <[email protected]>
Commit-Queue: Aaron Green <[email protected]>
  • Loading branch information
nopsledder authored and CQ Bot Account committed Feb 14, 2025
1 parent 4fc31df commit 3fc29bc
Show file tree
Hide file tree
Showing 23 changed files with 281 additions and 87 deletions.
10 changes: 5 additions & 5 deletions pw_allocator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ pw_size_table(

pw_size_diff(
name = "best_fit_size_report",
base = "//pw_allocator/size_report:base",
base = "//pw_allocator/size_report:detailed_block",
bloaty_config = "//targets/rp2040:bloaty_config",
label = "BestFitAllocator",
# TODO: https://pwbug.dev/388905812 - Make size reports always build.
Expand All @@ -843,7 +843,7 @@ pw_size_diff(

pw_size_diff(
name = "bucket_allocator_size_report",
base = "//pw_allocator/size_report:base",
base = "//pw_allocator/size_report:detailed_block",
bloaty_config = "//targets/rp2040:bloaty_config",
label = "BucketAllocator",
# TODO: https://pwbug.dev/388905812 - Make size reports always build.
Expand All @@ -853,7 +853,7 @@ pw_size_diff(

pw_size_diff(
name = "first_fit_size_report",
base = "//pw_allocator/size_report:base",
base = "//pw_allocator/size_report:detailed_block",
bloaty_config = "//targets/rp2040:bloaty_config",
label = "FirstFitAllocator",
# TODO: https://pwbug.dev/388905812 - Make size reports always build.
Expand All @@ -863,7 +863,7 @@ pw_size_diff(

pw_size_diff(
name = "tlsf_allocator_size_report",
base = "//pw_allocator/size_report:base",
base = "//pw_allocator/size_report:detailed_block",
bloaty_config = "//targets/rp2040:bloaty_config",
label = "TlsfAllocator",
# TODO: https://pwbug.dev/388905812 - Make size reports always build.
Expand All @@ -873,7 +873,7 @@ pw_size_diff(

pw_size_diff(
name = "worst_fit_size_report",
base = "//pw_allocator/size_report:base",
base = "//pw_allocator/size_report:detailed_block",
bloaty_config = "//targets/rp2040:bloaty_config",
label = "WorstFitAllocator",
# TODO: https://pwbug.dev/388905812 - Make size reports always build.
Expand Down
22 changes: 17 additions & 5 deletions pw_allocator/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -736,32 +736,43 @@ pw_size_diff("allocator_api_size_report") {
]
}

pw_size_diff("blocks_size_report") {
title = "Sizes of various block implementations"
binaries = [
{
target = "size_report:detailed_block"
base = "size_report:base"
label = "DetailedBlock"
},
]
}

pw_size_diff("block_allocators_size_report") {
title = "Sizes of various block allocator implementations"
binaries = [
{
target = "size_report:best_fit"
base = "size_report:base"
base = "size_report:detailed_block"
label = "BestFitAllocator"
},
{
target = "size_report:bucket_allocator"
base = "size_report:base"
base = "size_report:detailed_block"
label = "BucketAllocator"
},
{
target = "size_report:first_fit"
base = "size_report:base"
base = "size_report:detailed_block"
label = "FirstFitAllocator"
},
{
target = "size_report:tlsf_allocator"
base = "size_report:base"
base = "size_report:detailed_block"
label = "TlsfAllocator"
},
{
target = "size_report:worst_fit"
base = "size_report:base"
base = "size_report:detailed_block"
label = "WorstFitAllocator"
},
]
Expand Down Expand Up @@ -861,6 +872,7 @@ pw_doc_group("docs") {
report_deps = [
":allocator_api_size_report",
":block_allocators_size_report",
":blocks_size_report",
":concrete_allocators_size_report",
":forwarding_allocators_size_report",
"examples:custom_allocator_size_report",
Expand Down
56 changes: 41 additions & 15 deletions pw_allocator/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -220,65 +220,79 @@ TrackingAllocator
.. doxygenclass:: pw::allocator::TrackingAllocator
:members:

---------------
Utility Classes
---------------
In addition to providing allocator implementations themselves, this module
includes some utility classes.

.. _module-pw_allocator-api-block:

-----
Block
=====
-----
A block is an allocatable region of memory, and is the fundamental type managed
by several of the concrete allocator implementations. Blocks are defined
using several stateless "mix-in" interface types. These provide specific
functionality, while deferring the detailed representation of a block to a
derived type.
by several of the block allocator implementations.

.. tip::
Avoid converting pointers to allocations into ``Block`` instances, even if
you know your memory is coming from a ``BlockAllocator``. Breaking the
abstraction in this manner will limit your flexibility to change to a
different allocator in the future.

Block mix-ins
=============
Blocks are defined using several stateless "mix-in" interface types. These
provide specific functionality, while deferring the detailed representation of a
block to a derived type.

.. TODO(b/378549332): Add a diagram of mix-in relationships.
.. _module-pw_allocator-api-basic-block:

BasicBlock
----------
.. doxygenclass:: pw::allocator::BasicBlock
:members:

.. _module-pw_allocator-api-contiguous-block:

ContiguousBlock
---------------
.. doxygenclass:: pw::allocator::ContiguousBlock
:members:

.. _module-pw_allocator-api-allocatable-block:

AllocatableBlock
----------------
.. doxygenclass:: pw::allocator::AllocatableBlock
:members:

.. _module-pw_allocator-api-alignable-block:

AlignableBlock
--------------
.. doxygenclass:: pw::allocator::AlignableBlock
:members:

.. _module-pw_allocator-api-block-with-layout:

BlockWithLayout
---------------
.. doxygenclass:: pw::allocator::BlockWithLayout
:members:

.. _module-pw_allocator-api-forward-iterable-block:

ForwardIterableBlock
--------------------
.. doxygenclass:: pw::allocator::ForwardIterableBlock
:members:

.. _module-pw_allocator-api-reverse-iterable-block:

ReverseIterableBlock
--------------------
.. doxygenclass:: pw::allocator::ReverseIterableBlock
:members:

.. _module-pw_allocator-api-poisonable-block:

PoisonableBlock
---------------
.. doxygenclass:: pw::allocator::PoisonableBlock
Expand All @@ -293,26 +307,38 @@ produced.
.. doxygenclass:: pw::allocator::BlockResult
:members:

Block implementations
=====================
The following combine block mix-ins and provide both the methods they require as
well as a concrete representation of the data those methods need.

DetailedBlock
-------------
This type is not a block mix-in. It is an example of a block implementation that
uses the mix-ins above.
This implementation includes all block mix-ins. This makes it very flexible at
the cost of additional code size.

.. doxygenstruct:: pw::allocator::DetailedBlockParameters
:members:

.. doxygenclass:: pw::allocator::DetailedBlockImpl
:members:

---------------
Utility Classes
---------------
In addition to providing allocator implementations themselves, this module
includes some utility classes.

.. _module-pw_allocator-api-bucket:

Bucket
======
Buckets
=======
Several block allocator implementations improve performance by managing buckets,
which are data structures that track free blocks. Several bucket implementations
are provided that trade off between performance and per-block space needed when
free.


FastSortedBucket
----------------
.. doxygenclass:: pw::allocator::FastSortedBucket
Expand Down
14 changes: 14 additions & 0 deletions pw_allocator/code_size.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ of the interface is measured using an empty implementation,
.. .. include:: allocator_api_size_report
.. include:: ../size_report_notice

---------------------
Block implementations
---------------------
Most of the concrete allocator implementations are block allocators that use
:ref:`module-pw_allocator-design-blocks` to manage allocations. Code size and
memory overhead for blocks varies depending on what features are included.

The following are code sizes for the block implementations provided by this
module.

.. TODO: b/388905812 - Re-enable the size report.
.. .. include:: blocks_size_report
.. include:: ../size_report_notice

-------------------------------
Block allocator implementations
-------------------------------
Expand Down
88 changes: 54 additions & 34 deletions pw_allocator/design.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,10 @@ that combined all of their features and more. By decomposing allocators into
orthogonal behaviors, implementers can choose to pay for only those that they
want.

-----------------------------
Design of allocator utilities
-----------------------------
In addtion to providing allocator implementations themselves, ``pw_allocator``
includes some foundational classes that can be used to implement allocators.
.. _module-pw_allocator-design-blocks:

.. _module-pw_allocator-design-block:

pw::allocator::Block
====================
Blocks of memory
================
Several allocators make use of allocation metadata stored inline with the
allocations themselves. Often referred to as a "header", this metadata
immediately precedes the pointer to usable space returned by the allocator. This
Expand All @@ -102,31 +96,39 @@ header allows allocations to be variably sized, and converts allocation into a
matching that of the header type itself.

For ``pw_allocator``, the most common way to store this header is as a
:ref:`module-pw_allocator-api-block`. This class is used to construct a
doubly-linked list of subsequences of the allocator's memory region. It was
designed with the following features:

- **Templated offset types**: Rather than use pointers to the next and previous
blocks, ``Block`` uses offsets of a templated unsigned integral type. This
saves a few bits that can be used for other purposes, since the blocks are
always aligned to the block header. It also gives callers the ability to
reduce the size of the headers if the allocator's memory region is
sufficently small, e.g. a type of ``uint16_t`` could be used if the region
could hold no more than 65536 block headers.
- **Splitting and merging**: This class centralizes the logic for splitting
memory regions into smaller pieces. Usable sub-blocks can either be split from
the beginning or end of a block. Additionally, blocks from either end can be
split at specified alignment boundaries. This class also provides the logic
for merging blocks back together. Together, these methods provide the
invariant that a free block is only ever adjacent to blocks in use.
- **Validation and poisoning**: On every deallocation, blocks validate their
metadata against their neighbors. A block can fail to be validated if it or
its neighbors have had their headers overwritten. In this case, it's unsafe to
continue to use this memory and the block code will assert in order make you
aware of the problem. Additionally, blocks can "paint" their memory with a
known poison pattern that's checked whenever the memory is next allocated. If
the check fails, then some code has written to unallocated memory. Again, the
block code will assert to alert you of a "use-after-free" condition.
:ref:`module-pw_allocator-api-block`. Specific block implementations are created
by providing a concrete representation and implementing the required methods for
one or more of the block mix-ins. Each block mix-in provides a specific set of
features, allowing block implementers to include only what they need. Features
provided by these block mix-ins include:

- A :ref:`module-pw_allocator-api-basic-block` can retrieve the memory that
makes up its usable space and its size.
- A :ref:`module-pw_allocator-api-contiguous-block` knows the blocks that are
adjacent to it in memory. It can merge with neighboring blocks and split
itself into smaller sub-blocks.
- An :ref:`module-pw_allocator-api-allocatable-block` knows when it is free or
in-use. It can allocate new blocks from either the beginning or end of its
usable space when free. When in-use, it can be freed and merged with
neighboring blocks that are free. This ensures that free blocks are only ever
adjacent to blocks in use, and vice versa.
- An :ref:`module-pw_allocator-api-alignable-block` can additionally allocate
blocks from either end at specified alignment boundaries.
- A :ref:`module-pw_allocator-api-block-with-layout` can retrieve the layout
used to allocate it, even if the block itself is larger due to alignment or
padding.
- The :ref:`module-pw_allocator-api-forward-iterable-block` and
:ref:`module-pw_allocator-api-reverse-iterable-block` types provide iterators
and ranges that can be used to iterate over a sequence of blocks.
- A :ref:`module-pw_allocator-api-poisonable-block` can fill its usable space
with a pattern when freed. This pattern can be checked on a subsequent
allocation to detect if the memory was illegally modified while free.

In addition to poisoning, blocks validate their metadata against their neighbors
on each allocation and deallocation. A block can fail to be validated if it or
its neighbors have had their headers overwritten. In this case, it's unsafe to
continue to use this memory and the block code will assert in order make you
aware of the problem.

.. tip::
In the case of memory corruption, the validation routines themsleves may
Expand All @@ -137,6 +139,24 @@ designed with the following features:

.. _module-pw_allocator-design-metrics:

Buckets of blocks
=================
The most important role of a :ref:`module-pw_allocator-api-block_allocator` is
to choose the right block to satisfy an allocation request. Different block
allocators use different strategies to accomplish this, and thus need different
data structures to organize blocks in order to be able to choose them
efficiently.

For example, a block allocator that uses a "best-fit" strategy needs to be able
to efficiently search free blocks by usable size in order to find the smallest
candidate that could satisfy the request.

The :ref:`module-pw_allocator-api-basic-block` mix-in requires blocks to specify
both a ``MinInnerSize`` and ``DefaultAlignment``. Together these ensure that the
usable space of free blocks can be treated as intrusive items for containers.
The :ref:`module-pw_allocator-api-bucket` provide such containers to store and
retrieve free blocks with different performance and code size characteristics.

Allocator metrics
=================
A common desire for a project using dynamic memory is to clearly understand how
Expand Down
Loading

0 comments on commit 3fc29bc

Please sign in to comment.