在现代编程语言中,内存管理是一个核心议题。许多编程语言,如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位可执行文件