我正在尝试优化我的动态内存使用。问题是,我最初为从套接字获得的数据分配了一定数量的内存。然后,在新数据到达时,我重新分配内存,以便新到达的部分将适合本地缓冲区。经过一番摸索,我发现malloc实际上分配了比请求更大的块。在某些情况下更大;下面是malloc_usable_size(ptr)的一些调试信息:
请求284字节,分配320字节
请求644字节,重新分配1024字节
众所周知,malloc/realloc是非常昂贵的操作。在大多数情况下,新到达的数据将适合先前分配的块(至少当我请求644个字节而得到1024个字节时),但我不知道如何才能计算出这一点。
问题是malloc_usable_size不应该依赖(如手册中所述),如果程序请求644字节而malloc分配了1024字节,那么多余的644字节可能会被覆盖,无法安全使用。因此,对于给定的数据量使用malloc,然后使用malloc_usable_size来计算实际分配了多少字节,这不是一种可行的方法。
我想要的是在调用malloc之前知道块网格,所以我将请求比我需要的更大的最大字节数,存储分配的大小,并在realloc上检查我是否真的需要realloc,或者之前分配的块是否很好,只是因为它更大。
换句话说,如果我请求644字节,而malloc实际上给了我1024字节,我希望预测到这一点并请求1024字节。
根据您对libc
的具体实现,您将有不同的行为。我发现在大多数情况下有两种方法可以达到这个目的:
-
使用堆栈,这并不总是可行的,但是C允许vla在堆栈上,并且如果您不打算将缓冲区传递给外部线程
是最有效的while (1) { char buffer[known_buffer_size]; read(fd, buffer, known_buffer_size); // use buffer // released at the end of scope }
-
在Linux中,您可以很好地使用
mremap
,它可以在零拷贝保证的情况下扩大/缩小内存。它可能会移动你的VM映射。这里唯一的问题是,它只工作在块的系统页面大小sysconf(_SC_PAGESIZE)
,通常是0x1000
。void * buffer = mmap(NULL, init_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); while(1) { // if needs remapping { // zero copy, but involves a system call buffer = mremap(buffer, new_size, MREMAP_MAYMOVE); } // use buffer } munmap(buffer, current_size);
-
OS X通过Mach
vm_remap
具有与Linux的mremap
相似的语义,但它的编译性更强。void * buffer = mmap(NULL, init_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); mach_port_t this_task = mach_task_self(); while(1) { // if needs remapping { // zero copy, but involves a system call void * new_address = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); vm_prot_t cur_prot, max_prot; munmap(new_address, current_size); // vm needs to be empty for remap // there is a race condition between these two calls vm_remap(this_task, &new_address, // new address current_size, // has to be page-aligned 0, // auto alignment 0, // remap fixed this_task, // same task buffer, // source address 0, // MAP READ-WRITE, NOT COPY &cur_prot, // unused protection struct &max_prot, // unused protection struct VM_INHERIT_DEFAULT); munmap(buffer, current_size); // remove old mapping buffer = new_address; } // use buffer }
简短的回答是,标准的malloc接口不提供您正在寻找的信息。使用这些信息会破坏所提供的抽象。
- 重新考虑您的使用模式。也许可以在开始时预先分配一个缓冲区池,并在运行时填充它们。不幸的是,这可能会使您的程序比您希望的更复杂。 使用提供所需接口的不同内存分配库。不同的库在碎片、最大运行时间、平均运行时间等方面提供了不同的权衡。使用你的操作系统内存分配API。这些通常是为了高效而编写的,但通常需要系统调用(不像用户空间库)。
在我的专业代码中,我经常利用malloc()[等]分配的actual size
,而不是requested size
。这是我确定actual
分配size0的函数:
int MM_MEM_Stat(
void *I__ptr_A,
size_t *_O_allocationSize
)
{
int rCode = GAPI_SUCCESS;
size_t size = 0;
/*-----------------------------------------------------------------
** Validate caller arg(s).
*/
#ifdef __linux__ // Not required for __APPLE__, as alloc_size() will
// return 0 for non-malloc'ed refs.
if(NULL == I__ptr_A)
{
rCode=EINVAL;
goto CLEANUP;
}
#endif
/*-----------------------------------------------------------------
** Calculate the size.
*/
#if defined(__APPLE__)
size=malloc_size(I__ptr_A);
#elif defined(__linux__)
size=malloc_usable_size(I__ptr_A);
#else
!@#$%
#endif
if(0 == size)
{
rCode=EFAULT;
goto CLEANUP;
}
/*-----------------------------------------------------------------
** Return requested values to caller.
*/
if(_O_allocationSize)
*_O_allocationSize = size;
CLEANUP:
return(rCode);
}
我做了一些研究,发现了两件关于Linux和FreeBSD中malloc实现的有趣的事情:
1)在Linux中,malloc增量块以16字节的步骤线性递增,至少达到8K,所以根本不需要优化,这是不合理的;
2)在FreeBSD的情况是不同的,步骤更大,并且随着请求的块大小而增长。
因此,只有在FreeBSD上才需要任何类型的优化,因为Linux分配块的步骤非常小,并且从socket接收少于16字节的数据是非常不可能的。