首页 文章

预测C中的malloc块大小网格

提问于
浏览
1

我正在尝试优化我的动态内存使用情况 . 问题是我最初为从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 回答

  • 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通过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
    }
    
  • 1

    简短的回答是标准的malloc界面不提供您正在寻找的信息 . 使用该信息打破了提供的抽象 .

    一些替代方案是:

    • 重新考虑您的使用模式 . 也许在开始时预先分配一个缓冲池,随时填充它们 . 不幸的是,这可能会使您的程序比您想要的更复杂 .

    • 使用提供所需接口的其他内存分配库 . 不同的库在碎片,最大运行时间,平均运行时间等方面提供不同的权衡 .

    • 使用您的OS内存分配API . 这些通常被认为是高效的,但通常需要系统调用(与用户空间库不同) .

  • 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);
       }
    
  • 0

    我做了一些痛苦的研究,发现了两个关于Linux和FreeBSD中malloc实现的有趣的事情:
    1)在Linux中,malloc以16字节步长线性递增,至少高达8K,因此根本不需要优化,这是不合理的;
    2)在FreeBSD中情况不同,步骤更大,并且随着请求的块大小趋于增长 .
    因此,只有FreeBSD需要任何类型的优化,因为Linux以非常小的步长分配块,并且它不太可能从套接字接收少于16字节的数据 .

相关问题