BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / cpp / #72828同步于 2013/7/26
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖

关于内存的开销

puerxun
2013/7/26镜像同步8 回复
假设要弄一个N长的对象数组,每个对象的内存开销都是M。 方案1:Obj *objs = new Obj[N]。 方案2:Obj **objs = new Obj*[N].然后根据实际情况,等用到的时候,在new一个obj。 方案3:vector<Obj> objs.然后根据实际情况,等用到的时候,在new一个obj,push_back到objs中去。 如果只考虑内存的开销,三种方案的内存开销各是多少呢?
订阅后,新回复会通过你的通知中心匿名送达。
8 条回复
gaoweiwei机器人#1 · 2013/7/26
可以这样分析。堆中的内存对齐假设为8字节,分配一个块时,一般在块的头部和尾部会各包含四个字节的分配信息(比如块大小、是否分配等信息,可以参考《深入理解计算机系统》的那个模型。 方案1:M*N字节的有效载荷,8个字节的头尾信息,加上内存对齐,总共{M*N + 8},{.}表示对齐到8字节处。 方案2:指针的指针共{4*N + 8}字节,再加上实际的对象所占内存{M + 8} * (0 ~ N),总共{4*N + 8} + {M + 8} * (0 ~ N) 方案3:比较复杂,一般,P.J 版(VS2010)STL vector占16个字节(包含指向数据存储空间的头、尾、和可用尾的指针以及一个allocator),假设vector放在栈里,没有额外的内存开销。vector中的数据存储空间(不考虑SGI版 STL那样的第二级空间配置器)可以按上述方法算 {M * vector.capacity() + 8},。总共为16 + {M * vector.capacity() + 8},当然,capacity不是个常量。 以上为个人理解,出错勿怪。 【 在 puerxun 的大作中提到: 】 : 假设要弄一个N长的对象数组,每个对象的内存开销都是M。 : 方案1:Obj *objs = new Obj[N]。 : 方案2:Obj **objs = new Obj*[N].然后根据实际情况,等用到的时候,在new一个obj。 : ...................
puerxun机器人#2 · 2013/7/27
我实际跑的情况是:方案1与你分析的差不多,方案2与方案3内存占用差不多,但是远高于你分析的值,也就是,方案2中额外的开销不止是存指针的开销,由于vector对象上亿,方案3中的vector应该不是存在栈区,而且内存开销也与2相似。比较不理解的是方案2中还有什么地方有额外开销?难道是虚拟地址表的开销,大学学的都忘了唉。。。而方案3应该与其类似。 【 在 gaoweiwei 的大作中提到: 】 : 可以这样分析。堆中的内存对齐假设为8字节,分配一个块时,一般在块的头部和尾部会各包含四个字节的分配信息(比如块大小、是否分配等信息,可以参考《深入理解计算机系统》的那个模型。 : 方案1:M*N字节的有效载荷,8个字节的头尾信息,加上内存对齐,总共{M*N + 8},{.}表示对齐到8字节处。 : 方案2:指针的指针共{4*N + 8}字节,再加上实际的对象所占内存{M + 8} * (0 ~ N),总共{4*N + 8} + {M + 8} * (0 ~ N) : ...................
quan机器人#3 · 2013/7/27
第三个方案,vector的动态增长方式,默认是成倍增长的吧? 0, 1,2,4,8,16.。。。。。 方案二,new[]运算符,在分配到的内存前面会有一个空间来记录空间大小,以方便delete[],此外,new[]就没有更多额外的事情。 我觉得,大量额外的开销可能来自于对象本身?如果不是c类型的结构体,c++有可能做一些变态的事情。。例如偷偷增加一些字段,来做一些事情。。。LZ可以确认一下。。 例如 string a(“hello”),a.size()值为5,不过实际上,这个a占有的内存起码应是 12(用于计数方面) + 4(字符指针) + 5(hello的长度) + 1('\0',这是为了支持c_str()的操作), 一共是22. 如果再加入 string b= a, 那 a,b占的总内存是 22 + 12 + 4 = 38。。。 如果对a,或者b再进行一次修改,那又不是38了。。而是22*2 = 44
gaoweiwei机器人#4 · 2013/7/27
这个是纯粹的数据所占的内存,实际跑是怎么得到的数据?另外,方案三说vector有上亿个对象,是指vector中push_back了上亿个item吧?如果是,那对vector本身的内存占用没有影响,仍然是16个字节,vector中只有那三个指针(指向上亿个数据的内存区域)加一个allocator,还有vector存在栈区还是堆区,还是全局变量区取决于声明方式,跟数据量没有关系,帖子里说vector<Obj> objs,所以我推测是在栈区,要不就是在全局变量区,当然这个对内存开销影响不大。如果数据量达到上亿,那方案二确实会额外占据大量内存,因为每new一个对象都需要额外的8字节来保存分配信息以供delete可以正确释放内存,1亿个对象就需要额外8亿字节。你说方案2和方案3占用数据量差不多,按分析应该方案1和方案3占用内存差不多(假设1,2,3实际包含相同的对象数目),而方案2会更高很多,但既然2和3差不多,那可以推测3中的capacity要比实际的size高很多,即分配了大量的内存,你只用其中的一部分,极端的情况是只用了其中的一半(可以输出size和capacity来验证下)。 【 在 puerxun 的大作中提到: 】 : 我实际跑的情况是:方案1与你分析的差不多,方案2与方案3内存占用差不多,但是远高于你分析的值,也就是,方案2中额外的开销不止是存指针的开销,由于vector对象上亿,方案3中的vector应该不是存在栈区,而且内存开销也与2相似。比较不理解的是方案2中还有什么地方有额外开销?难道是虚拟地址表的开销,大学学的都忘了唉。。。而方案3应该与其类似。 :
gaoweiwei机器人#5 · 2013/7/27
3l说的也有道理,每个对象占用M你是怎么的出来的?我的以上分析只针对POD对象,如果对象内部还包含new出来的东西,那他实际占用的内存就跟用sizeof 的出来的就不一样了,这种情况下就得先分析每个对象实际占用内存数,然后再用2楼的公式计算。
puerxun机器人#6 · 2013/7/27
欢迎尝试: (尝试方案1最好要有5G+的空闲内存,方案2最好要有16G+的空闲内存,当然可以修改whole_length弄短一点。这个只是截取自一个程序的一部分:) #include <stdio.h> #include <vector> #include <ctime> using namespace std; struct edge{ unsigned int src; unsigned int dst; short sign; }; const int whole_length = 423347905; const int sig_length = whole_length / 10000; const int solution = 2; // 1 for solution 1 // 2 for solution 2 // 3 for solution 3 int main() { clock_t t = clock(); printf("Starting...\n"); vector<edge> edges_v; edge** edges = new edge*[whole_length]; edge *edgesx = new edge[whole_length]; unsigned int src_buffer, dst_buffer; short sign_buffer; int counter = 0; src_buffer = 0; dst_buffer = 0; sign_buffer = 0; for (int i = 0; i < whole_length; i ++) { if (solution == 1) { edgesx[counter].src = src_buffer; edgesx[counter].dst = dst_buffer; edgesx[counter].sign = sign_buffer; } else { edge *_edge = new edge; _edge->src = src_buffer; _edge->dst = dst_buffer; _edge->sign = sign_buffer; if (solution == 2) edges[counter] = _edge; else edges_v.push_back(*_edge); } counter ++; if (counter%sig_length == 0) { printf("Progress: %.4lf\r", double(counter)/whole_length); } } t = (clock() - t) / CLOCKS_PER_SEC; printf("Time cost: %d sec.\n", int(t)); } 【 在 gaoweiwei 的大作中提到: 】 : 这个是纯粹的数据所占的内存,实际跑是怎么得到的数据?另外,方案三说vector有上亿个对象,是指vector中push_back了上亿个item吧?如果是,那对vector本身的内存占用没有影响,仍然是16个字节,vector中只有那三个指针(指向上亿个数据的内存区域)加一个allocator,还有vector存在栈区还是堆区,还是全局变量区取决于声明方式,跟数据量没有关系,帖子里说vector<Obj> objs,所以我推测是在栈区,要不就是在全局变量区,当然这个对内存开销影响不大。如果数据量达到上亿,那方案二确实会额外占据大量内存,因为每new一个对象都需要额外的8字节来保存分配信息以供delete可以正确释放内存,1亿个对象就需要额外8亿字节。你说方案2和方案3占用数据量差不多,按分析应该方案1和方案3占用内存差不多(假设1,2,3实际包含相同的对象数目),而方案2会更高很多,但既然2和3差不多,那可以推测3中的capacity要比实际的size高很多,即分配了大量的内存,你只用其中的一部分,极端的情况是只用了其中的一半(可以输出size和capacity来验证下)。
gaoweiwei机器人#7 · 2013/7/27
#include <stdio.h> #include <vector> #include <ctime> using namespace std; struct edge{ unsigned int src; unsigned int dst; short sign; }; const int whole_length = 10000000;//423347905; const int sig_length = whole_length / 10000; const int solution = 3; // 1 for solution 1 // 2 for solution 2 // 3 for solution 3 int main() { printf("%d\n" , sizeof edge ); clock_t t = clock(); printf("Starting...\n"); vector<edge> edges_v; edge *edgesx; edge** edges; if (2 == solution) { edges = new edge*[whole_length]; } else if (1 == solution) { edgesx = new edge[whole_length]; } unsigned int src_buffer, dst_buffer; short sign_buffer; int counter = 0; src_buffer = 0; dst_buffer = 0; sign_buffer = 0; for (int i = 0; i < whole_length; i ++) { if (solution == 1) { edgesx[counter].src = src_buffer; edgesx[counter].dst = dst_buffer; edgesx[counter].sign = sign_buffer; } else { edge *_edge = new edge; _edge->src = src_buffer; _edge->dst = dst_buffer; _edge->sign = sign_buffer; if (solution == 2) edges[counter] = _edge; else edges_v.push_back(*_edge); } counter ++; if (counter%sig_length == 0) { printf("Progress: %.4lf\r", double(counter)/whole_length); } } t = (clock() - t) / CLOCKS_PER_SEC; printf("Time cost: %d sec.\n", int(t)); printf("size=%d, capa=%d\n", edges_v.size(), edges_v.capacity()); } 稍微修改了点源代码,以更方便测试。 测试数据,我的机器是CPU i5,操作系统32位ubuntu,编译器就是g++。N=10M, M=12.用top命令查看程序的虚拟内存总数记为测试数据。 方案1:实测117M内存,理论估算为120M,相差无几。 方案2:实测193M内存,按照2楼理论估算为4*10 + (12+8)*10=240,相差较多,猜测的原因是malloc在分配内存的时候只在堆内存块的头部记录了分配信息,没有尾部信息,这也是可以的,而且更高效,可以参考《深入理解计算机系统》虚拟内存那章。修正这个后,得到理论估计内存为4*10 + (12 + 4)*10=200M,与实测相差无几。 方案3:实测为347M。看输出vector的size=10M,但capacity=16M,计算下为12 * 16=192M,看代码可以发现,方案3包含了一部分方案2 的内存,参考上面的代码行号,因为在第64行new后,在第71行push进vector,此时实际上push进去的是 *_edge 的一个副本,原来的_edge没有delete掉,即存在两份_edge,一份在vector中,另一份就是new出来的那个啦,故总的内存还要加上后者共(12+4)*10 = 160M,总计192+160=352M, 与实测相差无几。 综上来看,2楼的分析得到了印证。 楼主的机器与操作系统想必是64位的,那计算的时候得把指针长度算作8个字节,把堆内存块的头部开销也算作8个字节,M要做内存对齐,也得用sizeof计算下。 【 在 puerxun 的大作中提到: 】 : 欢迎尝试: : (尝试方案1最好要有5G+的空闲内存,方案2最好要有16G+的空闲内存,当然可以修改whole_length弄短一点。这个只是截取自一个程序的一部分:) : [code=c] : ...................
W1039766642机器人#8 · 2013/8/2
【 在 puerxun 的大作中提到: 】 : 欢迎尝试: : (尝试方案1最好要有5G+的空闲内存,方案2最好要有16G+的空闲内存,当然可以修改whole_length弄短一点。这个只是截取自一个程序的一部分:) : [code=c] : ................... 代码写的pl。 看着很舒服。