Giter VIP home page Giter VIP logo

make_contiguous_objects's Introduction

make_contiguous_objects

Implementation for std::make_contiguous_objects proposal

Introduction

This proposal intends to add support to allocate adjacent objects of different types.

If you want to create a char next to an int, next to a long, you can do:

struct Struct { char c; int i; long l; };
auto* s = new Struct;

However, if you want to allocate an arbitrary number of chars next to several ints, next to multiple longs, there is no easy way to do so. In practice, developers will resort to splitting up the layout:

struct Struct { std::vector<char> cs; std::vector<int> is; std::vector<long> ls; };
auto* s = new Struct{numC, numI, numL};

Or, if performance is a requirement, developers will write by hand the code to allocate a blob of memory and place each object in the desired place.

With this proposal, it would be possible to write:

tuple<span<char>, span<int>, span<long>> s =
    std::make_contiguous_objects<char, int, long>(numC, numI, numL);

Motivation and Scope

Data structures that optimize for performance often use "contiguous objects" as a way to avoid multiple allocations and improve the memory locality.

std::shared_ptr<T[]>

One of the simplest examples is std::make_shared<T[]> which creates a std::shared_ptr<T[]> where the control block is adjacent to the T array. This simple operation involves a large amount of non-trivial code to:

  1. Calculate the exact amount of memory to allocate to hold the control block and Ts
  2. Find the correctly-aligned memory addresses where the control block and Ts will live
  3. Construct them while being aware that should exception be thrown, all objects must be destroyed in the reverse order of construction.

If a developer attempts to write such code for the first time, it will be either a multi-day task or many details will be overlooked. Generally, even experienced developers will avoid going into this realm due to the complexity of writing and maintaining this code.

For more details, see the libc++ implementation

Containers

Containers often need to store metadata adjacent to a group of elements.

Others

Proposed facilities

make_contiguous_objects

template<class... Args, class... Initializers>
auto make_contiguous_objects(Initializers... args) -> std::tuple<std::span<Args>...>

template<class Alloc, class... Args, class... Initializers>
auto make_contiguous_objects(Alloc&, Initializers... args) -> std::tuple<std::span<Args>...>

(1) Construct arrays of objects adjacent to each other in a single allocation.

(2) Same as (1) but using the allocator to allocate the storage and construct each object.

Initializers can be:

  1. size_t The number of elements in the array of that type
  2. std::arg(std::uninit_t, size_t count) Number of elements to be left uninitialized if trivially destructible. Note: In this case, the allocator won't be invoked.
  3. std::arg(std::ctor_t, size_t count, Args...) Number of elements and initialization parameters.
  4. std::arg(std::aggregate_t, size_t count, Args...) Number of elements and initialization parameters for aggregate init {}.
  5. std::arg(std::input_iterator_t, size_t count, InputIterator) Number of elements and an input iterator to provide values for the array
  6. std::arg(std::functor_t, size_t, Functor) Number of elements and a functor to provide values for the array

See an example of each being used

The return type is a tuple of std::span<T> pointing to each array.

destroy_contiguous_objects

template<class... Args>
void destroy_contiguous_objects(const std::tuple<span<Args>...>&);
template<class Alloc, class... Args>
void destroy_contiguous_objects(Alloc&, const std::tuple<span<Args>...>&);

(1) Call the destructor on all elements of all spans in the tuple in reverse order.

(2) Same as (1) but use the allocator to destroy objects and deallocate the storage

get_adjacent_address

template<class T, class U>
auto get_adjacent_address(U* u) -> T*;

Returns a pointer to T that is the next position after u that is suitable to hold such object. (Perhaps could be called realign_cast. I did not chose that option because I don't think the user should have to know about alignment to write code.)

It's guaranteed that get_adjacent_address of the end of a span returned by make_contiguous_objects match the begin of the next span.

Applying the proposed API in real code

Simplification of libc++ machinery for std::shared_ptr<T[]>

Simple shared ptr initial implementation

Discussion on common feedback

Why not RAII?

This API returns a tuple of spans and the caller is responsible for calling std::destroy_contiguous_objects passing that same tuple to destroy all objects and release the storage. It's obvious there is an opportunity for returning an object that manages the lifetime of the whole structure.

The current paper does not propose that because:

  1. It can easily be built on top of the raw API
  2. The author does not expect users of the API to store the whole result very frequently.

In most use-cases, the return value (tuple<span<Args>...>) will contain redundant information. For example, in a case where the arrays have the same size:

auto t = std::make_contiguous_objects<Metadata, Element>(numElements, numElements);

Users will generally store only the first pointer and derive the other arrays from that and numElements. See the application of this library on libc++ shared_ptr<T[]> in the section "Applying the proposed API in real code".

Require U for single objects, U[] for dynamic arrays and U[N] for static arrays to match std::shared_ptr

The main reason for this distinction (as far as I can tell) in shared_ptr API is the lack of a type dispatching mechanism to choose which constructor to use. So make_shared<T> allows passing multiple arguments that will be used for the construction of T inplace. make_shared<T[]> allows either passing nothing (new T()) or a single argument (new T(arg)). And finally make_shared_for_overwrite<T[]> gives the new T constructor, that leaves trivial types uninitialized.

This proposal intends to use the std::arg mechanism with type dispatching to choose any of those options, and others like input iterator constructor.

std::arg might be too "out there"

It's something we don't have in any other function in the standard and can be a very "open" API. Might bring a lot of controversy. Other options are:

  1. Chaining 1:
auto r = std::make_contiguous_objects<string, int, float>()
    .arg(std::ctor, 15, a, b,c)
    .arg(std::input_iterator, 10, it)
    .arg(std::uninit, 12);
  1. Chaining 2:
auto r = std::make_contiguous_objects()
    .arg<string>(std::ctor, 15, a, b,c)
    .arg<int>(std::input_iterator, 10, it)
    .arg<float>(std::uninit, 12);
  1. Limiting to always call the default constructor: std::make_contiguous_objects<int, float>(10, 20);

Either 1 or 2 should be viable options, while 3 could be too limited.

Bikeshed

  • std::make_objects
  • std::new_objects
  • std::allocate_objects
  • All of the above with adjacent_objects instead of objects
  • All of the above with arrays instead of objects

make_contiguous_objects's People

Contributors

brenoguim avatar tupaschoal avatar

Watchers

 avatar  avatar

Forkers

tupaschoal

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.