关于《C++设计新思维》中的“小型对象分配器”的疑惑

短歌如风 2003-10-09 03:44:38
在书中谈到一个块只容纳255个对象时说:
1:否则就不能分配大小小于sizeof(unsigned short)的对象
2:由于“内存对齐问题”,把任意的指针类型转换为unsigned short *不一定有效。

但是,为了在delete时可以知道对象的大小,要求对象必须可以使用RTTI,也就是说,必须有vtpr。书中的解决办法是所有的小型对象都从一个SmallObject类衍生,而这个类有一个虚析构函数,正确地解决了size问题,但同时也产生了另一个问题:
我们知道,由于SmallObject有一个虚析构函数:
class MyObject: public SmallObject
{
char data;
};
事实上内存结构相当于:
class MyObject
{
VTBL* vtpr;
char data;
};
很明显,assert(sizeof(MyObject) >= sizeof(VTBL*))这个断言永远成立。也就是说,本来就不可能分配大小小于的一个指针大小的对象。而指针大小是1的情况……

此外,如果sizeof(MyObject)是5,还是存在内存对齐问题,把任意指针转换成unsigned short *是不一定有效,但转换成VTBL**更不一定有效。

事实上,在要求对齐的32位系统中,这个对象的大小很可能是8。那么我们本来只需要一个字节就可以表示的数据,为了使用小型对象分配器,我们就要分配8个字节,而使用小型对象分配器本来就是为了节省空间,这不是一个矛盾吗?
...全文
119 21 打赏 收藏 转发到动态 举报
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
LoveCreatesBeauty 2004-02-09
  • 打赏
  • 举报
回复
学习
tiamo 2003-10-19
  • 打赏
  • 举报
回复
destructor 申明成virtual不是什么原因
而是一个基本的常识。。。。。。

一个要作为基类的类的destructor必须是virtual。。。。

magicblue 2003-10-13
  • 打赏
  • 举报
回复
1 我说的是 普通 的new,traditional c-sytle new。

2 SmallObject为了使用小型对象分配器的安全性上有所保证,就增加了virtual destructor,可见这最后一层包装并不对空间上的效率有什么保证。
短歌如风 2003-10-13
  • 打赏
  • 举报
回复

  我觉得xueweizhong的测试不具有代表性,因为没有考虑到标准内存管理函数使用的内存分配策略问题。这样的测试数据在面对“最先匹配原则”时如果初始内存块足够大的话决不会很慢。不过magicblue说“如果smallobject的速度和普通的new 相差无几,那这笑话就大了”也并不一定成立,new(准确地说是malloc)的实现也有很多方法去优化速度,并不只是简单的“在链表中查找”。因此我觉得SmallObjectAllocator的最大优点还是在于空间而不是时间。

  关于SmallObject的vtpr是否浪费,事实上,一般情况下,既然我们可以直接使用SmallObjectAllocator,当需要使用SmallObject时必然是需要一个vtpr的。比如一个不会有衍生类的“小型对象”就可以不从SmallObject继承:
class MyObject
{
public:
void operator delete(void * p)
{
SmallObject<>::operator delete(p, sizeof(*this));
}
};
(感觉这里loki的封装有问题,有些本应是Allocator的责任却加到了SmallObject上)
只有可以具有衍生类的类型的delete操作符才可能被衍生类使用从而无法直接确定大小,这时就需要一个vtpr来帮助编译器调用operator delete(void*,size_t)的版本(我上面说错了,这时并不需要RTTI,只是需要vtpr),但一个具有衍生类的基类,而且又能动态分配,并且还能使用基类指针类型进行释放操作……很明显,这时虚析构本来就是必需的,无论你是否使用operator delete (void*, size_t)。

当然,具有衍生类但不需要vtpr的情况可能也是有的,但实在少见。
magicblue 2003-10-12
  • 打赏
  • 举报
回复
我没说我崇拜他。我只是表明一个事实:他的loki不是一般人能写出来的,和一般人比起来他的确是个高手。当然了,这并不能保证他所写的smallobject有很快的速度。但是他的Modern C++ Design是一个出版物,拿给大家看的,对于这种很基本的东西,肯定是很有把握才敢这么说的。如果他连测试都没测试就花了整整一章来说实际效率很差的smallobject效率如何如何高,那这个就太傻了。就算是再有自信,或者再傻的人也不会傻到这个份上吧?我想说的是这个。而不是说我崇拜谁。
其实你说smallobject的效率很差,我的第一反应就是不太可能。
另外 绝对是个高手 和 是个绝对高手 的意思也差了不少^_^
xueweizhong 2003-10-12
  • 打赏
  • 举报
回复
>to magiclue
终于看到你的中文描述了。
不过我并不认为Andrei是什么绝对高手
^^^^^^^
想想当年他们也在comp.moderated.c++论坛论坛上
也作过一些很傻的描述。
大家都在学习和成长过程中,为什么要搞盲目崇拜呢?
magicblue 2003-10-12
  • 打赏
  • 举报
回复
Andrei 绝对是个高手,他不可能犯这么低级的错误。如果smallobject的速度和普通的new 相差无几,那这笑话就大了。
可能BC对小型对象的分配做了优化吧。
xueweizhong 2003-10-12
  • 打赏
  • 举报
回复
>to magicblue(小飞侠)
我原来是在CBUILDER5下测试的。速度几乎没什么改变。
今天我在VC++.net2003上测试了一下,
SmallObj比 ::operator new(delete)
快了很多。似乎Vc++的malloc比较慢?

然后我也试了一下SmallObjectDirect<Derived>
速度比SmallObj有快一点点。

等以后我在sun机器上再去试一下g++和CC的情况。

magicblue 2003-10-12
  • 打赏
  • 举报
回复
plainsong

there are some problems in my third reply, you are better delete it :)

xueweizhong

you don't need it anymore :)
and we indeed in a technological colony

smallobject seems to be a not so good wrap. deriving from it will inevitably incur overhead of 4 byte.(but i want to say, however, we need burden 4 byte or more in each object we want to allocating, but this burden of space has not direct relationship with burden of time. the important thing of which "Small object allocation mechanism" is speed, the less important thing is efficiency of space)
i find a document on line(it taking lots of my time^_^), there is a article about smallobject. i hope it can take effect about our question. it have a Benchmark of kinds of allcoation. the speed of smallobject is so fast

http://filodej.port5.com/Loki/SmallObjectAllocator.htm

my thought is loki is developing library, at present, it still is un-completed.
if you insist on getting to the bottom of the matter, maybe the only way is dropping a email to Andrei :)
xueweizhong 2003-10-11
  • 打赏
  • 举报
回复
我忘了:
struct Test : Loki::SmallObject<>
{
char a[89];
^^
};
这里缺省模板参数情况下SmallObject<>的数字‘89’应该
在 1-63 之间。

其实我用很多数字测试过,从小到大,效率上没什么改进。

>to magicblue(小飞侠)
能否用中文把你的观点简短地说清楚一点。
我私底下比较讨厌中国人写大段的洋文,好象到了殖民地似的。

housisong 2003-10-11
  • 打赏
  • 举报
回复

我的理解:

delete一个对象的问题
这是属于编译器魔法的一种;
各个编译器的实现也可能不一样;


比如:
1.编译器对每一个类硬塞进一个vtpr,delete时就可以通过RTTI来实现
2.调用new时作一点手脚,分配空间时多分配一点空间用来记录对象大小,delete时自然就可以通过传进来的指针得知分配的大小
3.没有vtpt时调用delete时由编译器自动生成类似这样的代码 delete(p,sizeof(p_type));

感觉第二种方案最好

magicblue 2003-10-10
  • 打赏
  • 举报
回复
xueweizhong:

1
if we can support more function(in fact, more is safety) and don't need to pay cash, why not to add a virtual destructor?
original text:
Most of the solutions listed assume you defined a virtual destructor for Base, which explains again why it is so important to make all of your polymorphic classes' destructors virtual. If you fail to do this, deleteing a pointer to a base class that actually points to an object of a derived class engenders undefined behavior. The allocator discussed herein will assert in debug mode and crash your program in NDEBUG mode. Anybody would agree that this behavior fits comfortably into the realm of "undefined."
To protect you from having to remember all this (and from wasting nights debugging if you don't), SmallObject defines a virtual destructor. Any class that you derive from SmallObject will inherit its virtual destructor. This brings us to the implementation of SmallObject.

2
using your code, writing Test* p = new Test; the overhead of vptr is not in memory that operator new allcoated. it is in the type information of p. why? take account of below original text:

typedef Singleton<SmallObjAllocator> MyAlloc;
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);
}

3
try:

char a[64];

and test again? this way maybe better
magicblue 2003-10-10
  • 打赏
  • 举报
回复
use SmallObject can support a trick that too large memory request will use traditional operator new rather than FixedSizeAllocator. this trick takes account of efficiency.

and C++ standard don't allow what you said about sizeof.
C++ standard says:
3.7.3.1
The pointer returned shall be suitably aligned so that it can be converted to a
pointer of any complete object type and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).
mechgoukiteng 2003-10-10
  • 打赏
  • 举报
回复
loki的内存池一向不是很高效的--里面还有vector。。。。

我用下来boost的pool是比较高效的,loki好在它封装的技巧比较好

所以可以用这样封装boost的pool
xueweizhong 2003-10-10
  • 打赏
  • 举报
回复
我理解的几点:

1:virutal-dtor没有必要
template<...>
class SmallObject : public ...
{
...
vitual ~SmallObject() {}
^^^^^^
};
因为我们不太可能把SmallObject<>作为基类来使用:
struct Test : SmallObject<> {...};
SmallObject<>* p = new Test;
delete p;
这里我们使用的时候关心的是Test的内容,
如果当成p来使用,得不到任何关于Test的信息,
这种情况绝对很少见。

2:基类也会占去几个字节
最好的情况下SmallObj<>是一个空基类
(如果把virtual-dtor去掉)
如果没有实现“空基类优化”技术,同样
会多出几个字节。

3:即使一个字节也不多,真能快很多吗?
我在CBUILDER5上用小程序测试了一把,
发现速度几乎相同:
#pragma pack(push, 1)
#include <loki/smallobj.h>
#include <boost/timer.hpp>
#include <boost/progress.hpp>

struct Test : Loki::SmallObject<>
{
char a[89];
};

struct Test1
{
char a[sizeof(Test)];
};

int main()
{
const int NUM = 59999;
{
boost::progress_timer t;
Test* p[NUM];
for (int i = 0; i < NUM; ++i)
p[i]=new Test;
for (int i = 0; i < NUM; ++i)
delete p[i];
}

{
boost::progress_timer t;
Test1* p[NUM];
for (int i = 0; i < NUM; ++i)
p[i] = new Test1;
for (int i = 0; i < NUM; ++i)
delete p[i];
}

{
boost::progress_timer t;
Test* p[NUM];
for (int i = 0; i < NUM; ++i)
p[i]=new Test;
for (int i = 0; i < NUM; ++i)
delete p[i];
}

{
boost::progress_timer t;
Test1* p[NUM];
for (int i = 0; i < NUM; ++i)
p[i] = new Test1;
for (int i = 0; i < NUM; ++i)
delete p[i];
}
}

4:加上多出的字节后,很显然
我的结论是:
SmallObj<>并没有传说中那么好,而且很糟糕。
短歌如风 2003-10-10
  • 打赏
  • 举报
回复
“为了在delete时可以知道对象的大小,要求对象必须可以使用RTTI,也就是说,必须有vtpr

上面的理解不对。为什么理解不对,可以看看C++书籍,基本的。”

我这里一本“基本的C++书籍”都没有,也不知道哪本书讲到这个问题了,希望wlfjck能仔细谈一谈。

cgsw12345说的没错,“對象的大小在編譯的時候已經確定了”,可惜的是,在delete时却无法确定对象类型,同样无法知道对象大小。至于“Object::operator delete(void*, size_t size)中是否能传进正确的size”和“Object是否需要有vptr”之间的关系,我不知道标准是怎样说的。

此外内存对齐并不只是为了效率,这一点在以前的几个关于内存对齐的讨论中已经说的很明白了。

感谢magicblue的提醒,我们使用SmallObjectAllocator时并不需要从SmallObject中衍生,可以直接使用,因此并不存在浪费空间的问题,是我没转过这个弯来,以为SmallObjectAllocator只是提供给SmallObject使用的。同样,在chuck内保证可以分配小于sizeof(unsigned short)大小的空间的能力是有意义的,因为它分配的不一定是一个SmallObject。我今天看了一下Loki的源代码,在RefCounted的实现中分配和释放引用计数是这样处理的:
pCount_ = static_cast<unsigned int*>(SmallObject<>::operator new(sizeof(unsigned int)));
SmallObject<>::operator delete(pCount_, sizeof(unsigned int));
而不是从SmallObject衍生出一个CondedInt类。看来SmallObject的真正意义还是为那些需要IS-A概念的多态对象准备的。不过我不明白它为什么不直接使用SmallObjectAllocator而要借助于SmallObject。

此外就是关于内存问题。比如我前面那个类定义,在要求4字节对齐的4字节指针编译器中是否保证sizeof(MyObject)==8永远成立?换句话说,是否保证(MyObject* p)这个断言成立:
assert((unsigned char *)(p + 1) == (unsigned char *)p + sizeof(MyObject))?
如果标准允许编译器在sizeof实现时只计算容纳对象要求的最小大小(因为后一个域不一定是要求对齐的数据类型)而只是在指针的加法计算时才进行必要的对齐调整以适应用指针访问数组的需要的话,那么小型对象分配器将在对齐问题上遇到巨大的问题。
hyifeng 2003-10-10
  • 打赏
  • 举报
回复
严重关注。
cgsw12345 2003-10-10
  • 打赏
  • 举报
回复
为了在delete时可以知道对象的大小,要求对象必须可以使用RTTI,也就是说,必须有vtpr

上面的理解不对。为什么理解不对,可以看看C++书籍,基本的。
----
對象的大小在編譯的時候已經確定了,所以不需在RTTI。


對齊問題在任何的編譯器中都存在,它本來一是用來取得更好存取效率。

配置器我也不是很理解,希望大家一起討論!
wlfjck 2003-10-10
  • 打赏
  • 举报
回复
为了在delete时可以知道对象的大小,要求对象必须可以使用RTTI,也就是说,必须有vtpr

上面的理解不对。为什么理解不对,可以看看C++书籍,基本的。
magicblue 2003-10-10
  • 打赏
  • 举报
回复
SmallObject class just is a warp of memory-request mechanism. it delegate memory requirement to its data member vector<FixedSizeAllocator>, FixedSizeAllocator forward request to chunk subsequently. finally memory is implemented by chunks. chunks is real memory rather than SmallObject. so the overhead will not appear in heap that allcoated. in contrast, it appear in stack generally. one SmallObject can allcoate plenty of memory with chunks. chunks stand for memory, it provides memory for you. SmallObjects stand for many chunks, it provides services to you. so your worried thing should aim at chunk rather than SmallObject
加载更多回复(1)

24,854

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 工具平台和程序库
社区管理员
  • 工具平台和程序库社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧