用C语言预测malloc块大小



我正在尝试优化我的动态内存使用。问题是,我最初为从套接字获得的数据分配了一定数量的内存。然后,在新数据到达时,我重新分配内存,以便新到达的部分将适合本地缓冲区。经过一番摸索,我发现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的具体实现,您将有不同的行为。我发现在大多数情况下有两种方法可以达到这个目的:

  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通过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接口不提供您正在寻找的信息。使用这些信息会破坏所提供的抽象。

  1. 重新考虑您的使用模式。也许可以在开始时预先分配一个缓冲区池,并在运行时填充它们。不幸的是,这可能会使您的程序比您希望的更复杂。
  2. 使用提供所需接口的不同内存分配库。不同的库在碎片、最大运行时间、平均运行时间等方面提供了不同的权衡。使用你的操作系统内存分配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字节的数据是非常不可能的。

相关内容

  • 没有找到相关文章

最新更新