详谈内存管理技术(一)

先自问一个问题:C++有几种new?

  我一直以为是两种:operator new 和 placement new。刚刚查了下,原来是3种:还有一个是new operator。而且,我还弄错了一个...但是,无论如何,我们能够改变的只有两个:

  1、operator new,分配内存。

  2、placement new,构造对象。

  而剩下的那个new operator很直白:负责调用上面两个new。也就是其仅仅是语法层次上的东西,用来产生operator new和placement new的语义。

 

  这是一个不错的开始,因为我要讲的“内存管理”,其实是上面所提到的所有:内存分配和对象构造。当然,还有一个对称的内存释放和对象析构,这些自然也会有,只是相对来说前者更加直白和重要。或者,我可以换一个题目:内存与对象管理技术。

  这将是一个,或者说数个相当大的话题;你不信,我可以给你一个列表(别眨眼):

  1、内存池,特别是一个可用的内存池,构建一个是相当困难的!其需要达到数个极其苛刻的要求:强悍的性能(否则我们还需要它?)、并发且线程安全(这点和上一点几乎是矛盾的存在)、高可用性(意味着其有着合理的回收机制,否则可能会浪费大量内存;而且能够处理相当畸形的环境,比如单一线程分配,单一线程回收)、可调试(简直不可能!!!)。

  2、线程模型,任何涉及并发的技术,都需要一个强大的线程模型,以提供各种额外的支持;为什么会提到它?内存分配不需要并发?不要开玩笑。没有TLS(线程本地存储),一切的并发都只能吃翔;而这却需要一个新的线程模型的支撑(系统的TLS,我个人而言,并不信任)。

  3、对象构造管理,这其实是一个很庞大的工程;其处于整个C++的最底层,也就意味着,我需要足够的努力才能够去完成我想要达到的目标——对于不同的类型,我能够做出正确且最好的选择,来构造它。

  4、类型系统,这是作为第三条的支撑性系统;如何达到上一条的目标?我需要一个类型系统,来提供足够的信息,以完成选择(需要注意的是:对象构造本身和类型系统,是独立的;只是恰巧,类型系统可以帮助对象获得最优的构造方式)。

  5、垃圾收集器,这是一个C++“永远的痛”,特别是当我用C++创造了一门有垃圾收集的脚本语言时,深感如此;所以,我陆续构建了两套垃圾收集系统,以C++可用的方式,当然会有各种限制。这里,提到的原因很简单:我们都梦想着能够用上或者创造一个属于C++的垃圾收集器!!!   特别是,在我做完上面的所有事情后。

  看到没有,上面任何一个部分,都是一个庞大的话题;所幸的是,这些我都完整地做了一遍(或多遍),我会慢慢地,详细到来。

  PS:写这部分,我是很高兴和激动的,因为,这正是我所最擅长的领域;而身边却没有一个可交流的人。

 

  一、内存池

  所有的STL容器,都有着一个不可忽视,但一直被无视的部分:内存适配器;其实,也就是一个预留的接口,某天我们能够使用更快的内存分配,来替换默认的malloc/free。毫无疑问,这就是内存池。许多书籍都提到并给出了详细的代码,来教会我们如何构建一个性能秒杀系统分配的单线程内存池;而在并发上,也就是支持多线程的内存池上,支支吾吾,止步于加锁...最后发现和系统分配并没有什么太大区别,毕竟系统分配也是加锁了的。

  当然,要构建一个可用的高性能多线程内存池,并不困难;国内的还有人,特意写了一本书,来讲解他自己命名的多线程内存池(当我发现他所实现的高端货,和我自己折腾出来的一模一样时,便没看了:因为,造出来所需要的努力,其实远比写一本书少的多;当然,后者钱要多得多)。其思路很简单:

  1、每一个线程一个内存池;通过TLS实现。

  2、一个所有线程共用的全局内存池;用来给线程内部的池,提供分配和回收服务;当然,这一级访问需要锁~

  3、线程内部池的分配回收策略(向共用池),共用池整体的分配回收策略(向系统)。

  之所以“简单”,是前两个部分,是很直白和自然的解决方案!只要你记住一点:我要并发,我要性能——请问TLS。而需要思考和抉择的是第三点;之所以需要“思考”,因为没有超大规模数据的支撑的前提下,任何的策略都只是我们的臆想而已,任何可能的畸形分配情形,都会使我们所“猜测”出的策略无效;而“抉择”,是在我们了解到了足够信息后,必然需要面对的(否则,系统分配,还会有存在的理由?)。

  所以,在面对一个“可用”的内存池时,我们需要足够的谨慎;而,我所要讲的就是这些“谨慎”。

 

  二、线程模型

  或许,我过于依赖TLS,因为我足够愚蠢:除此之外,我别无他法。

  在我所提到的内存池中所使用的关键技术之一便是TLS;同样的在垃圾收集器部分,也将大量运用到TLS技术。所以,是的我们无法避开线程模型:因为,TLS需要一些额外的支持,恰巧C++没有给我们(所以,我只能自己去造)。

  为什么,我不用Windows的TLS?主要原因是,我不信任它(属于个人直觉);另外的一部分,则是我的库中是尽量避免任何第三方依赖!包括STL,我也不会用到(所以,我自己写了一套;还有更深层次的原因:我不喜欢STL的现有部分接口方式)。

  什么是线程模型?我不知道。看到那么多人和大牛都在说,所以我也用了这个关键字。在我的库里就是:

  1、重新定义的一整套接口,用来提供完整的线程服务;我使用了类似Java的方式,只要继承了IThread,然后实现run,便可开启另外一个线程:

class MyThread: public IThread{
    void run(){
        ...
    }
};
MyThread thread;
thread.execute();//启动线程

  2、一个线程服务类,用于提供各种线程相关服务;其中最关键的服务就是TLS;还有一个稍微有一点霸气的功能:stop the world。也就是,它管理着当前所有运行着的线程,包括主线程。

  3、线程工具类,TaskRunner、Thread、Timer、ThreadPool提供了各种不同的线程支持。

  4、并发工具类,进行了抽象化后的锁和事件;当然重要的是锁:内核锁、临界区、读写锁。

  所有的这些都是简单而无聊的;但作为一个整体来提供,则需要付出许多额外的努力。

 

  三、对象构造

  用“构造”这个词来限定这个领域;很不正确,毕竟完整的是:无参数构造(默认构造)、有参数构造、复制构造、对象析构、对象移动。一共5个部分需要我们关心,如果你还不明白,看看下面的代码:

Something* addr = (Something*)std::malloc(sizeof(Something));

//1、无参数构造
new (addr)Something();//调用Something();

//2、有参数构造
new (addr)Something(12);//调用Something(int);

//3、复制构造
Something value;
new (addr)Something(value);//调用Something(const Something&);

//4、对象析构
addr->~Something();//调用~Something();

//5、对象移动
Something* other= (Something*)std::malloc(sizeof(Something));
new (other)Something(*addr);//将addr处的对象移到other
addr->~Something();//析构掉addr的对象;只保留other处的,保证“移动”语义

  上面都是最保守的方式;对于基本类型,以及自定义POD类型,我们并不需要这样“复杂”的调用:

int* addr = (int*)std::malloc(sizeof(int));

//1、无参数构造
//do nothing

//2、有参数构造
*addr = 12;

//3、复制构造
int value = 12;
*addr = value
//
memcpy(addr, &value, sizeof(int));

//4、对象析构
//do nothing

//5、对象移动
int* other= (int*)std::malloc(sizeof(int));
memcpy(other, addr, sizeof(int));

  这里似乎并没有太大的区别;在优化级别较高的情况下,自定义的POD也会生成第二种代码;但,但是,在构造一个数组时,这种区别将是巨大的:

//第一种:默认的方式
for(size_t i = 0; i != size; ++i){
    new (data + i)Something();
}

//第二种:最佳的方式
//do nothing

  第一种方式,在优化时依然会产生代码,当然是足够“优化”的代码;但在最佳的方式下,我们可以不产生任何代码。因为,编译器并不是足够聪明,许多自定义的POD,它并不能够以最佳的方式来生成代码;而,身为人类的我们,可以!

  当然,是通过类型系统。

 

  四、类型系统

  其实第三部分的“对象构造”本身是相当“精简”的,只是为了到达目的;我们需要额外的努力,而这“努力”,我通过类型系统,来具现化。

  很简单的方式:我来告诉编译器,生成怎样的代码!

  1、对于每个类型(特别是可以优化的类型),我们定义它的类型信息:是否是平凡构造/复制/析构,是否是内存可移动的等。

  2、通过模板元编程,萃取以上信息;得到更多丰富的类型信息。

  3、使用上述原始信息和萃取的信息;来完成我们所有想做的事情;当然,也是通过模板元编程。

  本质上,我们是在构建一个庞大的类型数据库;也就是,需要手动定义许多,编译器并不知道的信息。

  这部分,复杂度上是很简单的;但,又有但是,你会模板元编程么?!学习这一技术,本身就是一个痛苦且漫长的过程;更为关键的是,其编程方式,不同于在C++中的其他所有,你将必须接受其函数式的思维方式;而这,很痛苦。

 

  五、垃圾收集器

  这是一个令人兴奋的话题,在C++的世界里;但我们,要知道相当一部分有GC的语言,其本身就是用C++写的(JAVA、C#等);这真是一个悲伤的话题。

  所以,我通过不怎么努力的努力;在我的库里,完成里两套C++本身可用的垃圾收集器。当然,都有着一些限制;第一个版本,只能够在独立的线程里运行,线程间的交互需要额外的机制(并非不可以);第二个版本,则是一个更加可用的收集器,其没有线程限制。

  当然,这两个收集器,都是给我的脚本语言使用的;因为,在动态语言里,环形引用是常见的景象;基于引用计数的技术,在这一点上,毫无作为。

  在这个部分,我并不是教大家如何构造出一个C++可用或不可用的垃圾收集器;而是,带大家看看我所知道的,那些垃圾收集方案,其能够有着怎样的作为;关于这些,我有着一个长长的列表,这里我就不详细展开。但,大致有以下内容:

  1、C++可用的,简单的各种回收方式:shared_ptr/scoped_ptr、基于链表和栈对象(RAII)的收集器、侵入式智能指针。

  2、完整的大型收集器:标记清扫、标记缩并、节点复制、分代式收集等等;不,我只会讲我所熟悉的,如:标记清扫、节点复制、分代式。

  3、C++的世界中,我们需要垃圾收集器?如何?

  嗯,大致上就这些;但这部分将会是在相当久远的时候,才会与大家见面,那时,或许会有不同。

 

  总结下,内存管理,的确是一个相当大的话题。

  对了,有一句话,我一直想说:绝对,绝对,不要自作聪明地,重载全局new,绝对!!!   欲知为啥,见下期~(呃,下期有可能是隔壁《从RPC开始》的第三期......)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
随着技术的不断发展,无人机在低空经济中的应用越来越广泛。无人机的关键技术包括飞行控制系统、传感器技术、通信技术、能源技术等。 1. 飞行控制系统 飞行控制系统是无人机最核心的技术之一,主要包括姿态控制、导航控制、动力控制、任务控制等。姿态控制是指无人机的飞行姿态控制,如俯仰、横滚、偏航等。导航控制是指无人机的定位和导航控制,包括GPS、惯性导航等。动力控制是指无人机的动力系统控制,包括电机控制、电池管理等。任务控制是指无人机的任务执行控制,包括摄像机控制、传感器控制等。 2. 传感器技术 传感器技术是无人机的重要技术之一,主要包括视觉传感器、红外传感器、雷达传感器、激光传感器等。视觉传感器是无人机进行空中拍摄和图像识别的重要传感器,包括相机、红外相机等。红外传感器可以进行夜间和低能见度环境下的目标探测和跟踪。雷达传感器可以进行远距离目标探测和跟踪。激光传感器可以进行精确测距和三维建模。 3. 通信技术 通信技术是无人机的关键技术之一,主要包括无线通信、卫星通信、地面控制等。无线通信是无人机与地面控制站之间的重要通信方式,需要保证通信稳定和可靠。卫星通信可以提供更广泛的通信覆盖范围和更高的通信带宽。地面控制是无人机的重要控制方式,需要实现对无人机的遥控和数据传输。 4. 能源技术 能源技术是无人机的关键技术之一,主要包括电池技术、太阳能技术、燃料电池技术等。电池技术是无人机的主要能源来源,需要具备高能量密度、长寿命、快速充电等特点。太阳能技术可以为无人机提供额外的能源支持,但需要考虑太阳能的时间和地点限制。燃料电池技术可以实现无人机长时间、长距离的飞行,但需要解决燃料电池的稳定性和安全性问题。 综上所述,无人机的关键技术主要包括飞行控制系统、传感器技术、通信技术、能源技术等。这些技术的不断发展和创新将进一步推动无人机在低空经济中的应用和发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值