Giter VIP home page Giter VIP logo

flexclass's Introduction

Flexclass

A C++17 library to emulate C flexible array members. See the User Guide for a complete walkthrough.

Problem statement

Consider the following implementation of a Node in a graph structure:

struct Node
{
    std::size_t              id;
    void*                    userData {nullptr};
    std::unique_ptr<Node*[]> links;
};

Node* makeGraphNode(std::size_t id, const std::vector<Node*>& links)
{
    auto n = new Node{id};
    n->links = std::make_unique<Node*[]>(links.size());
    std::copy(links.begin(), links.end(), n->links.get());
    return n;
}

In this scenario, everytime makeGraphNode is called two allocations will occur:

  • One for links array
  • One for Node object itself

Multiple allocations can be costly and incur in memory fragmentation which reduce cache friendlyness.

Enter Flexclass

The same Node can be implemented with Flexclass:

struct Node
{
    auto fc_handles() { return fc::make_tuple(&links); }

    std::size_t      id;
    void*            userData {nullptr};
    fc::Array<Node*> links;
};

Node* makeGraphNode(std::size_t id, const std::vector<Node*>& links)
{
    auto n = fc::make<Node>(links.size()) (id);
    std::copy(links.begin(), links.end(), n->links.begin());
    return n;
}

The required changes are:

  • Declare fc_handles method that returns the required handles
  • Declare links as fc::Array<Node*> which is a special handle for an array
  • Construct the object with the syntax fc::make< T > ( array sizes ) ( T constructor arguments );

See this example on Compiler Explorer

To build the layout, Flexclass uses fc_handles to collect a list of array types to be built.

The layout of the Node is:

                                _______
                               |       |
                               |       V
| [std::size_t]   [void*]   [Node**] | [Node*] [Node*] [Node*] ... [Node*]
|       Id       UserData    Links   |
|                                    |
|                  Node              |

Now a single allocation is performed to store both the Node and the array of Node*.

One can reduce the size even more by using the fact that the Node* array will always be at the end of the Node:

fc::AdjacentArray<Node*> links;

Which would generate the following compact layout:

|[std::size_t]   [void*]      []    | [Node*] [Node*] [Node*] ... [Node*]
|      Id       UserData    Links   |
|                                   |
|                 Node              |

Flexclass is not limited to one array, so the following declaration is perfectly valid (godbolt link):

struct MyType
{
    auto fc_handles() { return fc::make_tuple(&a, &c); }

    fc::Array<int> a;
    std::string b;
    fc::Range<string> c;
    bool d;
};

Which will generate the following layout:

                                           _______________________________________________________________
                                          |                                                               |
                              ____________|_________________________________                              |
                             |            |                                 |                             |
     ________________________|____________|_________________                |                             |
    |                        |            |                 V               V                             V
|[int*] [std::string] [std::string*] [std::string*] [bool]| [int] ... [int] [std::string] ... [std::string]
|  a         b          c (begin)       c (end)       d   |
|                                                         |
|                       MyType                            |

Notice that fc::Range has an end pointer for the std::string array. Since std::string is non-trivially-destructible, Flexclass needs to iterate on the array to call destructors when destroying the MyType.

Feature summary

Range and Array

  • Range<T> requests pointers to both begin and end of the array (cost: 2 pointers)
  • Array<T> requests a pointer only to the begin of the array (cost: 1 pointer)

AdjacentArray and AdjacentRange

Adjacent handles deduce their begin from another element:

  • By default, they are adjacent to the base
  • They can also be adjacent to another array. See the user guide for the syntax.

Cost:

  • AdjacentArray<T> cost 0 pointers
  • AdjacentRange<T> cost 1 pointer

Cool Applications

Shared Array

A shared array implementation needs a reference counter and the data (in this case an array). So it can be modeled as:

template<class T> class SharedArray;
template<class T>
class SharedArray<T[]>
{
    struct Impl
    {
        auto fc_handles() { return fc::make_tuple(&data); }
        unsigned refCount;
        std::conditional_t<std::is_trivially_destructible_v<T>,
                          fc::Array<T>,
                          fc::Range<T>> data;
    };

  public:
    /* Interesting public API */
    static SharedArray make(std::size_t len) { return {fc::make<Impl>(len)(/*num references*/1u)}; }

    decltype(auto) operator[](std::size_t i) { return m_data->data.begin()[i]; }
    decltype(auto) operator[](std::size_t i) const { return m_data->data.begin()[i]; }

    auto use_count() const { return m_data ? m_data->refCount : 0; }

    /* Boilerplate */
    SharedArray(SharedArray&& other) : m_data(std::exchange(other.m_data, nullptr)) {}
    SharedArray(const SharedArray& other) { m_data = other.m_data; incr(); }
    SharedArray& operator=(SharedArray&& other) { decr(); m_data = std::exchange(other.m_data, nullptr); return *this; }
    SharedArray& operator=(const SharedArray& other) { decr(); m_data = other.m_data; incr(); return *this; }
    ~SharedArray() { decr(); }
  private:
    SharedArray(Impl* data) : m_data(data) {}
    void incr() { if (m_data) m_data->refCount++; }
    void decr() { if (m_data && m_data->refCount-- == 1) fc::destroy(m_data); }
    Impl* m_data {nullptr};
};

snippet source | anchor

TODO/Known issues

  • Provide ways to convert from a Adjacent member to base
  • Add range-for support for AdjacentRanges.
  • Use static asserts to provide better diagnostics on common mistakes:
    • Passing the wrong number of parameters
    • Instantiating a flexclass containing an undefined type
  • Check if available features are enough to replace code in LLVM (User/Uses classes)
    • They are not. Need to support arrays behind the object.
  • Documentation - review
  • Implement Optional or Maybe as a short-cut for an array with 0 or 1 element.
    • Then it would be possible to add an example of creating a mixin system
    • Or maybe just a MultiVariant<A, B, C>, where existing objects could be just "A", "B", "C", "A B", "B C", "A C" or "A B C"
      • what's the application for that?

flexclass's People

Contributors

actions-user avatar brenoguim avatar eullerborges avatar thamara avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

flexclass's Issues

Review documentation

I went through multiple iterations of this library, so both the Readme and the UserGuide may be out of date.

The task at hand is to review and reproduce examples and make sure they all compile and make sense.

Add more performance tests

We have a single benchmark using a graph. New cool applications to showcase the power of the library wanted.

Refactor 'basic' tests

The main test file (tests/unit/basic.test.cpp) contains a lot of duplicated code, it's not very thorough and doesn't test many of the corner cases.

It would be nice to cleanup, reuse, add more tests... Anything improving tests will count!

Implement arrays *behind* the user object

Today the library only creates the Object + arrays in that sequence.
It would be nice to have a "BackwardAdjacentArray" which would cause the layout to be:

 | arrays | user object | more arrays |

Extract the core engine (and make a paper out of it?)

The fundamental building blocks of this library can be seen as:

template<class... Ts>
auto allocate_adjacent_arrays(std::integral auto... sizes) -> std::tuple<std::pair<Ts*, Ts*>...>;

This function would allocate a single block of memory that fits sizeof...(Ts) arrays which the sizes are given in sizes. The return value returns pointers to (non-constructed) objects inside this.

The second building block would be something like std::uninitialized_value_construct which support multiple begin/end iterators.

Then it should be very easy to build this library.

Add code coverage

A lot of the code is templated but it would be nice to have code coverage infrastructure.

Make the wins of using the library more clear on "Problem Statement"

I have seen a lots of confused feedback from people who did not understand what the library does and how it helps.

It would be nice to have a more clear diagram showing layout differences, and have a smaller description that makes the advantages crystal clear. I really don't know how to explain it better :(

Add function documentation

The library has little to no comments. It would be great to go over all functions and tricky code and document it.

Add a unit test to verify size of the library after preprocess

One strong goal of this library is to include as few headers as possible. It is on purpose that we avoid <memory> and <tuple> headers.

In GCC, the library expands to 5000 lines today

The task at hand is to add test (somehow) to verify that the expanded code is less than 6000 lines.

Add performance verification test

The library contains a benchmark directory with a single benchmark. It would be nice to have a check that the performance using the library is actually better than the performance without it.

Not sure how to implement that. People with experience in Catch2 and cmake wanted :)

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.