- 浏览: 170305 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
boz.lee:
不错, 好多地方因为这里而出错
全局变量和局部变量在内存里的区别 -
issllx:
看到此文后对iteye深表遗憾
Yahoo S4 -
wohenshuaiba:
请问你有这个的代码吗?
k均值聚类(K-means) -
yxwang0615:
大神:
apr-1.3.3.tar.bz2
apr-util- ...
log4cxx编译方法 -
yxwang0615:
比csdn上的说的清楚干练,早看到这个就好了,我编了一天
p ...
log4cxx编译方法
小对象分配技术是Loki提高效率的有效途径,Loki里广泛使用小对象, 如果使用系统默认分配器,因为薄记得缘故,可能代价在400%以上,所以这个是必须要解决的问题。我们首先来谈Chunks。
1.MemControlBlock结构
{
bool available_;
MemControlBlock* prev_;
MemControlBlock* next_;
};
是的,你看到了,这里没有块大小标志,这个时候注意这点会对你理解Chunk代码很有帮助,因为MemControlBlock里没有保存块大小的字段,所以它的上层——Chunk必须每次在需要的时候传入,而且必须自己保证不会修改传递数字的大小,这是非常基础的假设,宛如二者是一个类,是一个整体,你不能进行自己给自己错误数据的方式进行测试,那肯定是极端疯狂者的做法或者有特殊原因。
明白了这点,在Chunk的代码中关于size_t的意义就非常好理解了。
2.Chunks结构
{
void Init(std::size_t blockSize,unsigned char blocks);
void Release();
void* Allocate(std::size_t blockSize);
void Deallocate(void* p,std::size_t blockSize);
unsigned char* pData_;
unsigned char firstAvailableBlock_;
unsigned char blockAvailable_;
};
我希望可以在声明上花多一点时间,这有助于你理解系统,从这个角度来说代码片段是没有任何意义的。
Init初始化本Chunk,它有若干MemControlBlock组成,你要指定块数量和块大小,特别要注意的是,如我们上边讨论的,一旦调用成功,你必须保证blockSize的大小每次都一样(永远不改变)。可以预期的是,在Init函数里使用了new方法开辟大块内存,以后每次Allocate都是回传特定一块。
对,还有一点要注意,基于Chunk的内存分配的块大小是在Init中已经确定的,是的,就是我反复强调的blockSize,你无法改变它,使用Allocate分派内存也是这个大小,而且每次一块,从这个层次上来说,这里的内存分配没有任何自由度,你可以认为这是new情况下的char,这是最小的分配单元。
有了以上两点注意,Allocate和Release的功能变的非常简单,取出一块特定大小的内存或归还一块内存。
pData_是Init中new出内存的位置。关于firstAvailableBlock_和blockAvailable_两个数据,是为了高效处理内存Allocate和Deallocate而定义的,使用它们,我们可以避免遍历查找内存块,尽管查找的效率好不错,但是不要忘了我们在做底层基础块,线性复杂度是我们十分不愿意看到的。
3.Chunk实现细节
为了高效查找、分配,我们在分配的时候对各个小块标记偏移位置,未分配的内存可以存放任何数据,所以在每个小块的开始存放偏移量是没有任何问题的,firstAvailableBlock记录第一个可用块,那查找该块的策略就是pData_ + blockSize × firstAvailableBlock,这是索引访问。唯一难理解的是归还内存。
我们知道,firstAvailableBlock指向第一块可用内存(如果有的话),归还的时候,它应该指向我们刚归还的那块,这很好处理firstAvailableBlock × blockSize = pcur - pData_ ,关键是firstAvailableBlock在归还前指向可用内存如何在以后被检索到,是的,这块内存也应该有偏移量记录,我们可以记录该可用内存的索引,也就是那一刻的firstAvailableBlock了,问题变的异常简单。
具体代码不再贴了,核心三个函数的功能已经讲明白了。
我们使用了偏移量(构造索引查询)得方法避免了检索,这是Chunk效率的核心部分,Chunk又是小对象分配技术的核心部分。
2.大小一致得分配器
Chunk可以分配固定大小的有限数量内存块,为了达到分配任意多个内存块的目的,我们需要另外一个策略满足这个需求,也就是对Chunk的进一步包装。
小对象的分配技术将固定大小内存分配做成两个层次,当然,更多时候这种划分里考虑效率问题,但是你可以用心体会这里的隔离思想。
FixedAllocator的思想异常简单,为了应对可能的数量不固定的内存块分配,它使用vector存放Chunks,这已经可以解决问题,唯一需要注意的是如何提高效率。
我们记录可用Chunk位置,当有分配请求的时候直接获取,如果可用Chunk用完,则引发一次线性查找,如果还是未找到,那引发一次分配。为了提高哪怕是一点点的归还效率,我们记录最后分配内存的Chunk的位置,在归还的时候优先查询,如果未找到(也就是该块待归还内存不在当前Chunk里),会引发一次线性查找。
是的,FixedAllocator中,查找开始变的多了起来,但是似乎不太容易找到更好的方案了。
为了进一步提高效率,可以使用高速缓冲的策略,归还的内存留待下次使用,而不是直接归还,这是个不错的想法,但是暂时我不优化这个,我更想说的是Loki的小对象分配策略,确切的说是它使用的结构和有限的技术细节。
有必要提一下FixedAllocator的结构和分配方法:
{
private:
std::size_t blockSize_;
unsigned char numBlocks_;
typedef std::vector<Chunk> Chunks;
Chunks chunks_;
Chunks* allocChunk_;
Chunks* deallocChunk_;
};
void* FixedAllocator::Allocate()
{
if(allocChunk_ == 0 || allocChunk_->blockAvailable_ == 0)
{
Chunks::iterator i = chunks_.begin();
for (;;++i)
{
if (i == chunks_.end())
{
chunks_.push_back(Chunk());
Chunk& newChunk = chunks_.back();
newChunk.Init(blockSize_,numBlocks_);
allocChunk_ = &newChunk;
deallocChunk_ = &chunks_.front();
break;
}
if (i->blocksAvailable_ > 0)
{
allocChunk_ = &*i;
break;
}
}
}
assert(allocChunk_ != 0);
assert(allocChunk_->blocksAvailable_ > 0);
return allocChunk_->Allocate(blockSize_);
}
3.SmallObjAllocator
如果第一次看到小型对象的分配细节,看完Chunk和FixedAllocator之后你一定很迷茫,在内存分配的起初两层,我们总是在研究固定大小的内存块,这似乎没有一点用处,大小一旦确定,你无法分配比blockSize大的内存块,即使是 blockSize得整数倍,因为一次只得到了一块内存,连续两次得到的内存块未必是连续的,你也不适合分配比blockSize小的内存块,这要浪费内存,而且很别扭。其实这都是因为Loki小对象分配器把Chunk和FixedAllocator做为原组使用了,真正分配任意大小内存的是SmallObjAllocator,看一下代码你就肯定明白了:
{
public:
SmallObjAllocator(std::size_t chunkSize,std::size_t maxObjectSize);
void* Allocate(std::size_t numBytes);
void Deallocate(void* p,std::size_t size);
private:
std::vector<FixedAllocator> pool_;
};
我们在构造函数里确定最大可分配的内存块大小,但是这并不意味着你只能分配不比maxObjectSize大的内存块,只是对于这样的内存块SmallObjAllocator转发给了系统的new。
我们已经知道,FixedAllocator只能分配固定大小的内存块,是的,到这里你明白了,pool_里有所有大小的Chunk,如此任意大小的块分配都可以转给合适的Chunk。在Deallocate里我们要求提供一个内存块大小的参数,避免pool_级别的遍历。
为了避免可能的内存浪费,pool_里的FixedAllocator并不是从1到maxObjectSize全部一次性分配的,一般情况下,一次性分配对内存的浪费是惊人的,相反,我们采用首次使用分配的策略。在首次使用的时候分配,依FixedAllocator里使用到的策略,我们对最后使用到的FixedAllocator记录以优化查询,最差情况下,SmallObjAllocator进行二分查找。
{
public:
...
private:
std::vector<FixedAllocator> pool_;
FixedAllocator* pLastAlloc_;
FixedAllocator* pLastDealloc_;
};
4.SmallObject 小对象分配策略在SmallObjAllocator层已经柳暗花明,可以猜测SmallObject是个顶级包装,使得使用更方便,确实是这样的,“SmallObject的定义 非常简单,只不过情节有点复杂”。 需要注意的一点是delete的类级别重载: 如你所见,delete haha得时候,我们定义的delete得到了调用,也就是编译器提供给了我们额外的一个参数size,这也是Andrei要讨论编译期获取size的策略的原因,编译器给我们传了额外参数,我们有必要了解一下编译器开发者怎么做的。但注意,只是了解,我觉得在这里明白类operator delete以及定义类类虚析构函数的重要性就可以了。 对于整个程序里的SmallObject而言,我们只需要一个SmallObjAllocator,这就是Singleton模式了,可以抢先欣赏一下Loki得SingletonHolder:
{
public:
static void* operator new(std::size_t size);
static void operator delete(void* p,std::size_t size);
virtual ~SmallObject(){}
};
{
public:
static void operator delete(void* p,std::size_t size)
{
cout<<"you call my delete"<<endl;
::operator delete(p);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Base* haha = new Base;
delete haha;
return 0;
}
void* SmallObject::operator new(std::size_t size)
{
return MyAlloc::Instance().Allocate(size);
}
void SmallObject::operator delete(void* p,std::size_t size)
{
MyAlloc::Instance().Deallocate(p,size);
}
发表评论
-
Techniques of Protocol Buffers Developer Guide
2010-12-07 10:39 1110Techniques Streaming Multip ... -
Protocol Buffer Basics: C++
2010-12-07 10:38 1024Protocol Buffer Basics: C++ ... -
Encoding of Protocol Buffers Developer Guide
2010-12-07 10:37 934Encoding A Simple Message ... -
Style Guide of Protocol Buffers Developer Guide
2010-12-07 10:36 777Style Guide This document pr ... -
Language Guide of Protocol Buffers Developer Guide
2010-12-07 10:34 1042Language Guide Defining A M ... -
Overview of Protocol Buffers Developer Guide
2010-12-07 10:31 672Developer Guide Welcome to t ... -
字节序
2010-12-01 21:03 942这几天又开始做网络方面的应用了,既然是网络编程,字节序肯定 ... -
全局变量和局部变量在内存里的区别
2010-11-09 21:55 2203一、预备知识—程序的内存分配 一个由c/C++编译 ... -
JobFlow要继续考虑的问题
2010-09-17 22:35 934小记: 1.把作业队列的概念扩展为作业池JobPool,提供 ... -
C++单例不可继承
2010-09-17 17:24 1476C++语言和单例的特性决定了单例不可继承。 单例有如下几项要 ... -
C++静态成员与静态成员函数小结
2010-09-16 15:48 1047类中的静态成员真是个让人爱恨交加的特性。我决定好好总结一下静态 ... -
log4cxx编译方法
2010-09-08 10:30 19731、下载http://logging.apache.o ... -
优秀的框架工具
2010-09-08 10:13 1124zookeeper Hadoop的子项目,可靠地分布式小文件 ... -
关于auto_ptr的忠告
2010-08-26 16:21 9091.auto_ptr不能用于数组,因为它调用的是delet ... -
snprintf与strncpy效率对比
2010-07-20 09:31 1564一直以为strncpy效率优于snprintf. 无意中在网上 ... -
用Valgrind查找内存泄漏和无效内存访问
2010-07-20 09:29 1946Valgrind是x86架构Linux上的多重用途代码剖析和内 ... -
头文件互包含于名字空间的相关问题
2010-07-17 20:27 1045c/c++里面头文件的使用 ... -
C++库介绍
2010-04-02 16:30 1498基础类1、 Dinkumware C++ ... -
C++中头文件相互包含的几点问题
2010-04-02 16:29 1013一、类嵌套的疑问 C++ ...
相关推荐
实验结果表明,应用小型对象分配技术后,系统在进行蠕虫仿真时的内存使用量得到显著降低,从而有助于提高仿真的规模和效率。 研究中还介绍了GTNetS的工作原理和特点,比如协议栈概念的引入,节点对象绑定多个网络...
### Java内存对象分配过程研究 #### 一、引言 Java作为一门强大的面向对象编程语言,在实际开发过程中,对象的创建及其内存管理是至关重要的环节。深入理解对象在内存中的分配过程不仅能够帮助开发者设计出更为...
2. 小型对象分配技术:针对小对象内存管理的优化技术,通常是为了减少内存碎片和提高内存利用率。例如,使用池分配器,将小对象集中分配在一个预先分配好的内存池中,以减少频繁的小块内存申请和释放带来的开销。 3...
小对象分配技术通常会使用池分配或位域技巧来优化内存的使用,以提高性能。 总的来说,《现代C++ PartI》提供了深入理解C++高级特性和最佳实践的机会,对于希望提升C++编程技能的开发者来说是一份宝贵的资源。通过...
而`fixobjallocator.h`可能是专门用于对象分配的内存分配器的头文件,它可能负责实际的内存管理,如对齐、碎片控制等,以进一步优化性能。 在实际应用中,对象池常用于数据库连接、线程、网络套接字、图形渲染对象...
传统的内存分配方式,如new运算符,通常为每个对象分配一个完整的内存块,对于小对象而言,可能会造成大量内存碎片。而子分配则是在较大的内存块(通常称为“arena”或“bucket”)内进行小块内存的分配,这样可以...
1. **分配内存**:首先为新创建的对象分配内存。 2. **零初始化**:将分配的内存初始化为零值。 3. **执行构造器**:调用对象的构造方法来设置初始状态。构造方法会按照声明顺序依次执行父类和本类的构造方法。 4. *...
通过为感兴趣的对象分配更多的比特,而对不重要的背景分配较少的比特,可以进一步提升编码效率。 #### 二、视频对象分割技术 ##### 2.1 视频对象的概念 在MPEG-4中,视频对象(Video Object, VO)指的是视频内容...
1. 分配内存:JVM在堆内存中为新对象分配空间。堆是Java中存储对象的主要区域,由垃圾收集器管理。 2. 初始化成员变量:所有实例变量被初始化为默认值,或者如果提供了初始化器,则使用指定的值。 3. 调用构造函数:...
为成本对象标识输入内部作业分配.doc
总结来说,小块内存分配器是提高C++程序内存管理效率的有效工具,尤其在处理大量小对象时。通过自定义的分配器类,我们可以控制内存的分配和释放,降低系统调用开销,减少碎片,从而提升程序运行速度。在深入理解和...
本讲义主要讨论了面向对象的核心技术,主要包括封装、类的继承以及方法的重写。 封装:在Java语言中,通过类(class)来实现封装,类可以定义私有成员变量(private)和公共方法(public)。私有成员变量只能在类的...
- 行为型模式:策略、模板方法、观察者、迭代器、访问者、责任链、命令、备忘录等,关注对象间的交互和职责分配。 5. UML统一建模语言: - 类图:展示类、接口、关联、继承关系等。 - 用例图:描述系统参与者与...
静态成员在类加载时就被初始化,而实例成员在创建对象时才分配内存。 9. **方法重载与重写**:方法重载(Overloading)是指在同一类中定义多个同名方法,但参数列表不同。方法重写(Overriding)是子类对父类已有的...
7. **虚析构函数**:如果基类包含动态分配的资源,需要确保在对象销毁时正确清理,此时应声明虚析构函数。 通过深入理解和实践这些C++的面向对象技术,不仅可以提升编程能力,还能更好地设计和维护复杂的软件系统。...