我正在尝试优化动态内存使用量。问题是我最初为从socket获得的数据分配了一些内存。然后,在新数据到达时,我正在重新分配内存,以便新到达的部分将适合本地缓冲区。经过一番探讨后,我发现malloc实际上分配的块数大于请求数量。在某些情况下显着更大;这里有来自malloc_usable_size(ptr)的一些调试信息:
请求284个字节,分配320个字节
请求644个字节,重新分配1024个字节
众所周知,malloc / realloc是昂贵的操作。在大多数情况下,新到达的数据将适合先前分配的块(至少当我请求644个byes并获得1024个)时,但我不知道如何解决这个问题。
麻烦的是不应该依赖malloc_usable_size(如手册中所述),如果程序请求644字节并且malloc分配1024,则多余的644字节可能被覆盖并且不能安全使用。因此,对于给定数量的数据使用malloc,然后使用malloc_usable_size来确定实际分配的字节数是不可行的。
我想要的是在调用malloc之前知道块网格,因此我将完全请求所需的最大字节数,存储已分配的大小以及realloc检查是否真的需要重新分配,或者如果之前分配块很好,因为它更大。
换句话说,如果我要请求644个字节,而malloc实际上给了我1024个,我希望预测到并请求1024个字节。
答案 0 :(得分:2)
根据您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与Linux mremap
通过Mach vm_remap
具有相似的语义,但它更加强化了。
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
}
答案 1 :(得分:1)
简短的回答是标准的malloc界面不提供您正在寻找的信息。使用这些信息打破了提供的抽象。
一些替代方案是:
答案 2 :(得分:0)
在我的专业代码中,我经常利用malloc()[etc]分配的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);
}
答案 3 :(得分:0)
我做了一些痛苦的研究,发现了两个关于Linux和FreeBSD中malloc实现的有趣的事情:
1)在Linux中,malloc增量块以16字节步长线性增加,至少高达8K,因此根本不需要优化,这是不合理的;
2)在FreeBSD中情况不同,步骤更大,并且随着请求的块大小趋于增长
因此,只有FreeBSD需要任何类型的优化,因为Linux以非常小的步骤分配块,并且它不太可能从套接字接收少于16字节的数据。