深入理解内存管理:从C语言到现代编程实践

在现代编程语言中,内存管理是一个核心议题。许多编程语言,如C++和Java,都提供了不同的内存管理机制。在C语言中,程序员需要手动管理内存,而在Java中,垃圾回收机制会自动处理内存。本文将探讨内存管理的重要性,从C语言的起源到现代编程实践,以及如何优化内存使用和提高程序性能。

C语言是一种接近汇编的语言,它在资源受限的计算机上运行,其中内存是非常宝贵的资源。即使在今天,仍然可以在汽车钥匙扣、洗衣机或剃须刀等微型微控制器中找到C或C++代码。编程微控制器是一种乐趣,但必须理解,在只有2000字节内存的情况下节省32字节与在有4亿字节内存的情况下节省内存是完全不同的。编译器似乎并不在意这个问题,算法在这两种情况下几乎相同,几乎没有优化。malloc()函数以不小于2的幂次方的块大小(16、32、64...字节)分配内存,主要是为了对齐目的,这使得处理器更加高效。必须包括内部控制数据的大小在块中。这就是为什么程序在请求比所需内存少一点时不会挂起的原因之一(例如,忘记字符串的尾随空大小),也是像valgrind这样的包能够检测到代码中的这一事实的原因之一。

内存分配的挑战

新的内存分配不是问题。libc只需要在堆的顶部取一块内存并缩短剩余的块。问题出现在释放一个不在顶部的已使用内存块时。这会创建一个“洞”,必须重用。堆通过维护一个已删除块的链表来解决这个问题。当需要一个新的块时,malloc()必须遍历这个列表,寻找块。此外,相邻的块会合并在一起,以保持堆尽可能不碎片化。

基准测试

为了防止停止阅读,并确保完成家庭作业,这里是测试程序的一个快照(包含在文章中)。看看“改进”列。它显示了与标准内存函数相比的速度增加,以及程序在版本中使用的内存量。一个有趣的事实是,vc6的速度是标准malloc()的120倍,而MinGW“只有”21倍,但在realloc()中,比率是45到300倍。原因在下面探讨,但realloc()并不像malloc()和free()那样重要,因为在真正的程序中(特别是在C++中的new()和delete())几乎不使用。

提出的改进

让采取一种推测性的方法来解决这个问题。malloc()和free()通常以特定的方式从所有可能的情况中使用。这两个函数在new()和delete()操作符下,以及在真实程序中拥有的对象类型是有限的。检查堆作为C++程序“加热”时,块的大小会以固定大小的集合组织,对应于对象。当请求一个块时,malloc()会遍历已删除的链表,寻找足够大的块。free()关注的是保持内存不碎片化,合并相邻的空闲块在许多时候变得无用,因为请求的块的固定性质。

提议只使用固定大小的块(2的幂),并以LIFO方式保持释放的块在单个链表中。这允许插入(free)和移除(alloc)一个块可以在一步中完成,大大提高了操作速度。当所需的内存块不存在时,将请求标准malloc(),一旦进入列表就永远不会退出(直到程序结束)。这种方法的代价是每个块中未使用的内存或多或少是总块大小的25%。作为一个副作用,realloc()在基准测试中快了100倍。

使用代码

使用代码真的很简单。必须使用unalloc()、unfree()和unrealloc()代替malloc()、free()和realloc()。这里是用来测试的代码,可能是一个很好的工作示例。Win32缺少times(),所以在演示代码中有一个替代方案来模拟它。

C++ #define POOL_SIZE 1024 #define ITERATIONS 1000000 #define ROUNDS 7 unsigned randSize( ) { unsigned rnd= rand(); return (( rnd & 0x7FFF ) << 1 ); } unsigned randIdx( ) { unsigned rnd= rand(); return ( rnd & (POOL_SIZE- 1 ) ); } void mallocTest( int round ) { static void * buffer[ POOL_SIZE ]; // Zero init static void * unbuffer[ POOL_SIZE ]; int loop; unsigned tm1,tm2; for ( loop= 0 , tm1= times( NULL ) ; loop < ITERATIONS ; loop++ ) { int idx= randIdx(); if ( buffer[ idx ] ) { free( buffer[ idx ] ); } buffer[ idx ]= malloc( randSize() ); } tm1= times( NULL ) - tm1; for ( loop= 0 , tm2= times( NULL ) ; loop < ITERATIONS ; loop++ ) { int idx= randIdx(); if ( unbuffer[ idx ] ) { unfree( unbuffer[ idx ] ); } unbuffer[ idx ]= unalloc( randSize() ); } tm2= times( NULL ) - tm2; printf( " round %d libc ticks: %6u umem ticks: %6u " , round, tm1, tm2 ); tm1 *= 100 ; tm1 /= tm2; tm1-= 100 ; printf( " improving: %5u%%\n" , tm1 ); }

可以尝试不同的POOL_SIZE、ITERATIONS和randSize()值来测试改进。

文件列表

包含在ualloc_source.zip中的文件: main.c: 测试程序 memory.c: memory.h的源代码 memory.h: 上面的包含文件 windows.c: UNIX的times()函数的Windows模拟 unalloc.cbp: code:::blocks的项目文件 build.bat: Windows的构建文件。 build: UNIX的构建文件。 bin/debug/unalloc.exe: MinGW 32位可执行文件 vc6: Visual Studio项目 vc6/debug/unalloc.exe: Visual Studio 32位可执行文件

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485