预测C中的malloc块大小网格

时间:2014-06-16 15:05:17

标签: c memory-management malloc

我正在尝试优化动态内存使用量。问题是我最初为从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个字节。

4 个答案:

答案 0 :(得分:2)

根据您libc的具体实施情况,您将有不同的行为。在大多数情况下,我发现了两种方法:

  1. 使用堆栈,这并不总是可行,但C允许堆栈上的VLA,如果您不打算将缓冲区传递给外部线程,则最有效

    while (1) {
        char buffer[known_buffer_size];
        read(fd, buffer, known_buffer_size);
        // use buffer
        // released at the end of scope
    }
    
  2. 在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);
    
  3. 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界面不提供您正在寻找的信息。使用这些信息打破了提供的抽象。

一些替代方案是:

  1. 重新考虑您的使用模式。也许在开始时预先分配一个缓冲池,随时填充它们。不幸的是,这可能会使您的程序比您想要的更复杂。
  2. 使用提供所需接口的其他内存分配库。不同的库在碎片,最大运行时间,平均运行时间等方面提供不同的权衡。
  3. 使用您的操作系统内存分配API。这些通常被认为是高效的,但通常需要系统调用(与用户空间库不同)。

答案 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字节的数据。