#include <iostream>
#include <cstdlib>
#include <iostream>
#include <memory_resource>
#include <vector>
int main() {
char buffer[32] = {};
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';
}
pmr大概意思是“多态的内存资源”。C++03时代的STL容器使用的是被称为分配器allocator的内存管理模块,allocator是无状态(stateless)的。你会发现allocator的定义里没有成员变量,全是成员函数和一些typedef。自定义的allocator,也只能通过全局变量获取自己需要的外部信息(回到用全局变量做消息沟通的时代)。allocator是STL容器模板签名的一部分,不同的allocator模板实参具体化成一个新的容器类。例如:std::vector《int》 v1和 std::vector《int,my_allocator》 v2是两个完全无关的类型,他们之间不能之间做对象的赋值。也就是v1 = v2; 会引起编译错误。
pmr这个新的名字空间里,引入了另一种不同思路的分配器,当然也要配合全新的一套容器模板类。这个新的分配器叫做pmr::polymorphic_allocator; 这个分配器是stl分配器兼容的,唯一区别是,它是个有状态的(statefull),简单理解就是这个类具有一个数据成员memory_resource*。这个类memory_resource就是一个接口类,定义了一些分配内存,回收内容的虚函数。简单的说,把内存请求,内存释放的功能分离出来到memory_resource的子类实现了。
既然分配器做了重大设计,容器的设计也要改,他们应允许在构造自己时,传入一个分配器或者直接传入一个memory_resource的指针。
下面,介绍例子代码
char buffer[32] = {};
std::fill_n(std::begin(buffer), std::size(buffer)-1, '_');
std::cout << buffer << '\n';
就是开辟了一块位于栈上的32字节空间,用横杠填入每个字符,除了最后一个保留'\0'
std::pmr::monotonic_buffer_resource pool{
std::data(buffer), std::size(buffer)
};
上面这段话的意思是,创建了一个“单增内存资源”的实例对象,名字是pool。pool背后实际有两个后盾,第一个后盾很明显,就是刚才创建的那个char buffer[32]数组。第二个后盾不是很明显,叫做upstream。这个upstream就是另一个memory_resource对象,当char buffer[32]不够用时,对内存的请求就会转向upstream。是的,32字节的数组,很可能不够用。
std::pmr::vector<char> vec{&pool};
很明显,这是个新的vector的实现,它在pmr名字空间呢。使用传入的指针。我们知道,只有指针或者引用,才能实现多态,所以传入一个指针一点也不奇怪。monotonic_buffer_resource类继承自memory_resource。
for (char ch='a'; ch <= 'z'; ++ch)
vec.push_back(ch);
std::cout << buffer << '\n';
开始向vector中push_back传入a-z, 再次观察buffer,cout的输出为:
_______________________________
aababcdabcdefghabcdefghijklmnop
第一行是一堆横杠,显然是开始fill_n调用的结果。
第二行,显示规律为:a(1) ab(2) abcd(4) abcdefgh(8) abcdefghijklmnop(16)
这不就是vector的指数级分配规律吗?当vector扩容时,按2倍空间扩容。
但是,为什么monotonic_buffer_resource不从头分配呢?这就是与它的名字monotonic有关了。
它是一个只吃不拉的貔貅,只知道拿新的,不知道还旧的。还有这种应用场合吗?当然非常广泛了,如果你不知道,就要记住了。比如:我不停的分配10000块小块内存,做处理。最后有两个方法归还:方法1,调用10000次归还内存的方法。方法2:如果这10000块内存来自一个完整的大块,其实我只需归还这一大块一次就可以了。
另一个问题,那个q-z的数据哪里去了? 这就涉及到upstream,由它分配一块内存了。我们可以自定义一个memory_resource,只是用来观察。
#include <iostream>
#include <cstdlib>
#include <iostream>
#include <memory_resource>
#include <vector>
class debug_resource : public std::pmr::memory_resource {
public:
explicit debug_resource( std::pmr::memory_resource* up = std::pmr::get_default_resource() )
: _upstream{ up }
{ }
void* do_allocate(size_t bytes, size_t alignment) override {
std::cout << " do_allocate(): " << bytes << '\n';
return _upstream->allocate(bytes, alignment);
}
void do_deallocate(void* ptr, size_t bytes, size_t alignment) override {
std::cout << " do_deallocate(): " << bytes << '\n';
_upstream->deallocate(ptr, bytes, alignment);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
private:
std::pmr::memory_resource* _upstream;
};
int main() {
char buffer[32] = {};
std::fill_n(std::begin(buffer), std::size(buffer)-1, '_');
std::cout << buffer << '\n';
debug_resource dr;
std::pmr::monotonic_buffer_resource pool{
std::data(buffer), std::size(buffer),
&dr
};
std::pmr::vector<char> vec{&pool};
for (char ch='a'; ch <= 'z'; ++ch)
vec.push_back(ch);
std::cout << buffer << '\n';
}
我们发现,当pmr::vector需要64字节内存时,monotonic_buffer_resource无法满足需求,继而转向了dr请求64字节,但是dr也没法满足(它只是打印信息),dr把请求转发给自己的上游upstream,实际就是系统默认的一个std::pmr::get_default_resource()。 估计是执行new,delete.
现在话题又回到monotonic_buffer_resource和vector配合的动机上,我们想利用栈空间存储
a-z,不想用有点低效的heap空间。如何修改一下程序满足这个要求呢?用vector的reserve() 。
int main() {
char buffer[32] = {};
std::fill_n(std::begin(buffer), std::size(buffer)-1, '_');
std::cout << buffer << '\n';
debug_resource dr;
std::pmr::monotonic_buffer_resource pool{
std::data(buffer), std::size(buffer),
&dr
};
std::pmr::vector<char> vec{&pool};
vec.reserve(26); // 预先分配足够空间,避免二次分配
for (char ch='a'; ch <= 'z'; ++ch)
vec.push_back(ch);
std::cout << buffer << '\n';
}
输出:
_______________________________
abcdefghijklmnopqrstuvwxyz_____
这完全符合预期!
参考:https://blog.feabhas.com/2019/03/thanks-for-the-memory-allocator/