C++17的话题已经完成了十五期,今天将要给大家介绍下C++17中提出的一个重要的工具——PMR,全称为:polymorphic memory resource。翻译成中文就是:多态内存资源。PMR在C++ 17版本中还属于试验性质,正式使用是在C++20版本,因此如果想要尝鲜的话大家可以升级版本到C++20版本。
1 PMR初探
想要了解PMR是怎样管理内存的,需要先回忆一下C++17之前内存资源的管理。大家都知道长久以来,如果我们申请的资源要么是在栈上要么是在堆上,在栈上分配的是由编译器根据需要自动分配的,不需要手动对资源进行释放和清理。在堆上则是根据实际编程需要主动使用new操作符进行申请,使用完毕后在使用delete操作符进行手动释放和清理。在堆上申请资源虽然可以满足我们的要求但同时也伴随着副作用,如:
在C++17之前,我们使用的很多标准容器底层也是通过new和delete进行分配资源的。如我们熟悉的std::string。如果存储的字符串比较短如小于15字节长度的时候是默认在栈上存储。当字符串长度大于15时,string就会在堆上申请资源以满足动态扩展的功能。从C++17开始,标准委员会给我们提供了PMR,它可以使用一个事先定义好的连续空间,只要后续的所有操作能够在事先定义好的资源范围内使用就不会触发new和delete操作符的调用。PMR的操作主要被封装在std::pmr域名空间中。包含两个基础的类std::pmr::memory_resource和std::pmr::polymorphic_allocator。它们在std::pmr中的定义如下:
//std::pmr::memory_resource,抽象接口
class memory_resource;
//std::pmr::polymorphic_allocator,多态资源分配器
//C++17版本中的定义
template< class T >
class polymorphic_allocator;
//C++20版本中定义方式
template< class T = std::byte >
class polymorphic_allocator;
类模板 std::pmr::polymorphic_allocator 是一个分配器 (Allocator) ,展现出取决于其构造所用的 std::pmr::memory_resource 的不同的分配行为。因为 memory_resource 使用运行时多态管理分配器,以polymorphic_allocator 为其静态分配器类型的分配器可互操作,但能表现为如同它们拥有不同的分配器类型。polymorphic_allocator 的所有特化均满足分配器完整性要求。
2 PMR使用
先看下面这段代码,代码演示了PMR的使用方式:
int main()
{
std::array<char, 10> memory;
std::pmr::monotonic_buffer_resource pool{memory.data(),memory.size()};
std::pmr::vector<std::string> container{&pool} ;
for(int i=0;i<100;i++)
{
container.push_back("this is a test for use pmr and you know the length id over 15");
}
new_delete_sumary();
return 0;
}
需要注意的是在pmr域名空间内部,定了一套和stl标准容器一模一样的容器,两者是完全兼容,如上面就使用了pmr内部定义的vector和标准stl下面的string。在代码一开始定义了一个数组,然后在这个数组的基础上定义了std::pmr::vector
#new: 100 #del:0 #bytes:6200
从结果可知,代码运行完成发生了100次内存重分配,共6200个字节。这个结果也验证了前面我们所说的,下面将插入的字符串减小。使其长度小于10后,代码运行的结果为:-
#new: 0 #del:0 #bytes:0
从结果可知,当每次插入的字符长度小于数组大小时,就不会触发new的调用。这也说明,如果在栈上预先定义的内存大小能够满足代码生命周期的内存需要时就可以不触发内存的重新分配,从而提升代码运行效率。下面再对上面的代码进行修改,进行一次对比试验。首先将std::string修改成使用pmr下面的string。插入一个很大的字符串时会不会触发new的调用。
int main()
{
std::array<char, 10> memory;
std::pmr::monotonic_buffer_resource pool{memory.data(),memory.size()};
std::pmr::vector<std::pmr::string> container{&pool} ;
for(int i=0;i<100;i++)
{
container.push_back("this is a test for use pmr and you know the length id over 15");
}
new_delete_sumary();
return 0;
}
运行结果为:
#new: 0 #del:0 #bytes:0
从结果可知,使用std::pmr::string后即使插入的字符串长度大于预先定义的数组长度也并没有触发new操作符的调用。这是因为:上面代码在使用了pmr域名空间下面定义的vector和 string,他们就可以同时使用pool上申请的资源。如果有超出的话在pmr下面还定义了synchronized_pool_resource类。它主要负责管理预先分配的内存资源。它的角色更像是一个内存池,可以从中获取需要的资源,如果内存池中的资源不足时会继续从它的上游分配器中获取更大的内存块以满足需要,每次获取的内存块大小是以几何级数递增的。与synchronized_pool_resource类相似的还有一个unsynchronized_pool_resource,它们的区别是前者为线程安全类,后者线程不安全。但是它们的实现都是继承了std::pmr::memory_resource。如代码所示:
class synchronized_pool_resource : public std::pmr::memory_resource;
class unsynchronized_pool_resource : public std::pmr::memory_resource
3 PMR如何避免产生内碎片
在PMR中,内存申请是单调增长的,只有在整个对象销毁时才会对申请的资源进行释放,因此在使用的过程中可以避免内存碎片的产生。为了更直观的验证上面的说法,这里需要从std::pmr::memory_resource中派生一个类打印出资源申请过程。
class track_resource : public std::pmr::memory_resource {
public:
track_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] = {};
track_resource tr;
std::pmr::monotonic_buffer_resource pool{
std::data(buffer), std::size(buffer),
&tr
};
std::pmr::vector<char> vec{&pool};
for(int i=0;i<32;++i)
{
vec.push_back(i+'A');
}
std::cout << buffer <<";vec大小:"<<vec.size()<<std::endl;
}
代码运行后结果如下:
do_allocate(): 64
AABABCDABCDEFGHABCDEFGHIJKLMNOP;vec大小:32
do_deallocate(): 64
从上面的结果可以看出,vec每次进行扩展后之前分配的内存都没有进行释放。如果想在运行过程中vec不进行扩展可以调用reserve接口一次分配完毕,这样做可以节省内存。如下:
std::pmr::vector<char> vec{&pool};
vec.reserve(32);
for(int i=0;i<26;++i)
{
vec.push_back(i+'A');
}
std::cout << buffer <<";vec大小:"<<vec.size()<<std::endl;
修改完代码运行后,结果为:
ABCDEFGHIJKLMNOPQRSTUVWXYZ;vec大小:26
从运行结果可知,将vec预留足够的空间就就没有进行内存的重分配。
4 PMR内存耗尽时抛出异常
pmr可以从内存池中不断的申请资源,直到内存的耗尽。内存耗尽时monotonic_buffer_resource可以抛出一个异常。代码如下所示:
std::pmr::monotonic_buffer_resource pool{
std::data(buffer), std::size(buffer),
std::pmr::null_memory_resource()
};
如上,当没有足够的内存进行分配时就会抛出一个异常。
5 标准容器和pmr域名下的容器大小
如下面代码所示:
int main() {
std::cout << "标准容器大小:" <<sizeof(std::string)<<std::endl;
std::cout<<"std::pmr::string大小:"<<sizeof(std::pmr::string)<<std::endl;
}
代码运行结果为:
标准容器大小:32
std::pmr::string大小:40
如上,pmr下的容器比标准容器要大,主要是因为多态分配器多了一个指向内存资源的指针,也正是因为这样,多态分配器也被称为是有状态的。标准容器里面并没有这样一个指针。
6 标准容器和pmr域名下的容器性能
这里以vector为例进行验证,测试代码如下:
void TestPmrVec(){
char buffer[100] = {0};
std::pmr::monotonic_buffer_resource pool{
std::data(buffer), std::size(buffer)
};
std::pmr::vector<int> vec{&pool};
PerfSum t;
for(long i=0;i<1000000;i++){
vec.push_back(i);
}
std::cout<<"End"<<std::endl;
}
void TestStdVec(){
std::vector<int> vec ;
PerfSum t;
for(long i=0;i<1000000;i++){
vec.push_back(i);
}
std::cout<<"End"<<std::endl;
}
int main() {
std::cout<<"标准容器插入1000000次:"<<std::endl;
TestStdVec();
std::cout<<"pmr容器插入1000000次:"<<std::endl;
TestPmrVec();
}
运行结果如下:
标准容器插入1000000次:
End
统计性能结束,时间过去了149毫秒
pmr容器插入1000000次:
End
统计性能结束,时间过去了307毫秒
从运行结果看,pmr下vector的push_back比标准容器下要慢。这个结果实际上和试验结果是相违背的。理论上来说std::vector动态扩展是在堆上分配。std::pmr::vector在栈堆上,相比之下std::pmr::vector应该更快。但是试验结果反而变慢了,由此可见std::pmr在实际使用时并不像我们以为的那么乐观。自然慢的原因可能有很多,按照个人理解可能是:
7 总结
关于pmr,我们使用的还比较少,对于pmr底层的一些了解可能还存在不足。本文如有不足之处欢迎大家留言评论或者加微信好友讨论。
另外本文所有代码编译环境均在https://www.onlinegdb.com/编译通过,C++ 20版本。
8 参考
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/xTcTKjzG-lWUPfcekfhEzA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。