Polymorphic Allocators from C++17, std:vector Growth and Hacking

栏目: IT技术 · 发布时间: 3年前

内容简介:The concept of a polymorphic allocator from C++17 is an enhancement to standard allocators from the Standard Library.It’s much easier to use than a regular allocator and allows containers to have the same type while having a different allocator, or even a

Polymorphic Allocators from C++17, std:vector Growth and Hacking

The concept of a polymorphic allocator from C++17 is an enhancement to standard allocators from the Standard Library.

It’s much easier to use than a regular allocator and allows containers to have the same type while having a different allocator, or even a possibility to change allocators at runtime.

Let’s see how we can use it and hack to see the growth of std::vector containers.

  • Core elements of pmr:
    • new_delete_resource()
    • null_memory_resource()
    • synchronized_pool_resource
    • unsynchronized_pool_resource
    • monotonic_buffer_resource
    • A Much Better Solution

In short, a polymorphic allocator conforms to the rules of an allocator from the Standard Library. Still, at its core, it uses a memory resource object to perform memory management.

Polymorphic Allocator contains a pointer to a memory resource class, and that’s why it can use a virtual method dispatch. You can change the memory resource at runtime while keeping the type of the allocator. This is the opposite to regular allocators which make two containers using a different allocator also a different type.

All the types for polymorphic allocators live in a separate namespace std::pmr (PMR stands for Polymorphic Memory Resource), in the <memory_resource> header.

The Series

This article is part of my series about C++17 Library Utilities. Here’s the list of the articles:

Polymorphic Allocators from C++17, std:vector Growth and Hacking

Resources about C++17 STL:

OK, let’s go back to our main topic: PMR.

Core elements of pmr :

Here’s a little summary of the main parts of pmr :

  • std::pmr::memory_resource - is an abstract base class for all other implementations. It defines the following pure virtual methods:
    virtual void* do_allocate(std::size_t bytes, std::size_t alignment)
    virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
    virtual bool do_is_equal(const std::pmr::memory_resource& other) const noexcept
    
  • std::pmr::polymorphic_allocator - is an implementation of a standard allocator that uses memory_resource object to perform memory allocations and deallocations.
  • global memory resources accessed by new_delete_resource() and null_memory_resource()
  • a set of predefined memory pool resource classes:
    synchronized_pool_resource
    unsynchronized_pool_resource
    monotonic_buffer_resource
    
  • template specialisations of the standard containers with polymorphic allocator, for example std::pmr::vector , std::pmr::string , std::pmr::map and others. Each specialisation is defined in the same header file as the corresponding container.
  • It’s also worth mentioning that pool resources (including monotonic_buffer_resource ) can be chained. If there’s no available memory in a pool, the allocator will allocate from the “upstream” resource.

And we have the following predefined memory resources:

new_delete_resource()

It’s a free function that returns a pointer to a global “default” memory resource. It manages memory with the global new and delete .

null_memory_resource()

It’s a free function that returns a pointer to a global “null” memory resource which throws std::bad_alloc on every allocation. While it sounds not useful, it might be handy when you want to guarantee that your objects don’t allocate any memory on the heap. Or for testing.

synchronized_pool_resource

This is a thread-safe allocator that manages pools of different sizes. Each pool is a set of chunks that are divided into blocks of uniform size.

unsynchronized_pool_resource

A non-thread-safe pool_resource .

monotonic_buffer_resource

This is a non-thread-safe, fast, special-purpose resource that gets memory from a preallocated buffer, but doesn’t release it with deallocation. It can only grow.

An Example

Below you can find a simple example of monotonic_buffer_resource and pmr::vector :

#include <iostream>
#include <memory_resource>   // pmr core types
#include <vector>            // pmr::vector

int main() {
    char buffer[64] = {}; // a small buffer on the stack
    std::fill_n(std::begin(buffer), std::size(buffer) - 1, '_');
    std::cout << buffer << '\n';

    std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};

    std::pmr::vector<char> vec{ &pool };
    for (char ch = 'a'; ch <= 'z'; ++ch)
        vec.push_back(ch);

    std::cout << buffer << '\n';
}

Possible output:

_______________________________________________________________
aababcdabcdefghabcdefghijklmnopabcdefghijklmnopqrstuvwxyz______

In the above example, we use a monotonic buffer resource initialised with a memory chunk from the stack. By using a simple char buffer[] array, we can easily print the contents of the “memory”. The vector gets memory from the pool (and it’s super fast since it’s on the stack), and if there’s no more space available, it will ask for memory from the “upstream” resource. The example shows vector reallocations when there’s a need to insert more elements. Each time the vector gets more space, so it eventually fits all of the letters. The monotonic buffer resource doesn’t delete any memory as you can see, it only grows.

We could also use reserve() on the vector, and that would limit the number of memory allocations, but the point of this example was to illustrate the "expansion" of the container.

I mentioned that if the memory ends then the allocator will get memory from the upstream resource. How can we observe it?

Some Hacks

At start let’s try and do some hacking :)

In our case, the upstream memory resource is a default one as we didn’t change it. That means new() and delete() . However, we have to keep in mind that do_allocate() and do_deallocate() member functions also take an alignment parameter.

That’s why if we want to hack and see if the memory is allocated by new() we have to use C++17’s new() with the alignment support:

void* lastAllocatedPtr = nullptr;
size_t lastSize = 0;

void* operator new(std::size_t size, std::align_val_t align) {
#if defined(_WIN32) || defined(__CYGWIN__)
    auto ptr = _aligned_malloc(size, static_cast<std::size_t>(align));
#else
    auto ptr = aligned_alloc(static_cast<std::size_t>(align), size);
#endif

    if (!ptr)
        throw std::bad_alloc{};

    std::cout << "new: " << size << ", align: " 
              << static_cast<std::size_t>(align) 
              << ", ptr: " << ptr << '\n';

    lastAllocatedPtr = ptr;
    lastSize = size;

    return ptr;
}

In the above code part I implemented aligned new() (you can read more about this whole new feature in my separate article: New new() - The C++17’s Alignment Parameter for Operator new() ).

And you can also spot two ugly global variables :) However, thanks to them we can see when our memory goes:

Let’s reconsider our example:

constexpr auto buf_size = 32;
uint16_t buffer[buf_size] = {}; // a small buffer on the stack
std::fill_n(std::begin(buffer), std::size(buffer) - 1, 0);

std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)*sizeof(uint16_t)};

std::pmr::vector<uint16_t> vec{ &pool };

for (int i = 1; i <= 20; ++i)
    vec.push_back(i);

for (int i = 0; i < buf_size; ++i)
    std::cout <<  buffer[i] << " ";

std::cout << std::endl;

auto* bufTemp = (uint16_t *)lastAllocatedPtr;

for (unsigned i = 0; i < lastAllocatedSize; ++i)
    std::cout << bufTemp[i] << " ";

This time we store uint16_t rather than char .

The program tries to store 20 numbers in a vector, but since the vector grows, then we need more than the predefined buffer (only 32 entries). That’s why at some point the allocator turns to global new and delete.

Here’s a possible output that you might get:

new: 128, align: 16, ptr: 0x21b3c20
1 1 2 1 2 3 4 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 0 0 0 0 0 .....
delete: 128, align: 16, ptr : 0x21b3c20

It looks like the predefined buffer could store only up to 16th elements, but when we inserted number 17, then the vector had to grow, and that’s why we see the new allocation - 128 bytes.

The second line shows the contents of the custom buffer, while the third line shows the memory allocated through new() .

Here’s a live version @Coliru

A Much Better Solution

The previous example worked and shows us something, but hacking with new() and delete() is not what you should do in production code. In fact, memory resources are extensible, and if you want the best solution, you can roll your resource!

All you have to do is to implement the following:

  • Derive from std::pmr::memory_resource
  • Implement:
    do_allocate()
    do_deallocate()
    do_is_equal()
    
  • Set your custom memory resource as active for your objects and containers.

And here are the resources that you can see to learn how to implement it.

Summary

Through this article, I wanted to shows you some basic examples with pmr and the concept of a polymorphic allocator. As you can see, setting up an allocator for a vector is much simpler than it was with regular allocators. There is a set of predefined allocators at your disposal, and it’s relatively easy to implement your custom version. The code in the article showed just a simple hacking to illustrate where the memory is pulled from.

Back to you:

Do you use custom memory allocators?

Have you played with pmr and polymorphic allocators from C++?

Let us know in comments.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

轻松学算法

轻松学算法

赵烨 / 电子工业出版社 / 2016-7 / 69.00元

《轻松学算法——互联网算法面试宝典》共分为12 个章节,首先介绍了一些基础的数据结构,以及常用的排序算法和查找算法;其次介绍了两个稍微复杂一些的数据结构——树和图,还介绍了每种数据结构和算法的适用场景,之后是一些在工作与面试中的实际应用,以字符串、数组、查找等为例介绍了一些常见的互联网面试题及分析思路,便于读者了解这些思路,顺利地通过互联网公司的面试;最后介绍了一些常见的算法思想,便于读者对今后遇......一起来看看 《轻松学算法》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具