【问题标题】:Predict malloc block sizes grid in C预测 C 中的 malloc 块大小网格
【发布时间】:2014-08-06 10:58:20
【问题描述】:

我正在尝试优化我的动态内存使用情况。问题是我最初为从套接字获得的数据分配了一些内存。然后,在新数据到达时,我正在重新分配内存,因此新到达的部分将适合本地缓冲区。经过一番摸索,我发现 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 上检查我是否真的需要重新分配,或者如果以前分配的块很好,因为它更大。

换句话说,如果我要请求 644 字节,而 malloc 实际上给了我 1024,我希望预测到这一点并请求 1024。

【问题讨论】:

  • 这是在哪台电脑上运行的?在过去的二十年里,我没有使用任何计算机/操作系统,我认为这样的重新分配是一项昂贵的操作。您实际测量过需要多长时间吗?
  • 我在 Linux 和 FreeBSD 上使用 GCC。
  • 您要求的东西不是标准要求的。除此之外,大多数实现分配器使用对齐/页面算法来调整分配大小。如果您想扩展分配有malloccallocrealloc 的动态块,请执行您现在正在做的事情:使用realloc()。如果您要扩展的大小已经适合先前分配的页面,则成本将是微不足道的,并且将返回原始指针(同样,这是实践;不是由标准ttbomk)。如果请求的大小太小,无论如何都会进行新的分配+复制。
  • @gnasher729 我认为他的意思是相对于基本操作(例如添加)而言昂贵,而不是绝对时间单位。堆管理例程可能会在 malloc 发生时运行,例如块合并,这可能会导致 malloc 比平时花费更长的时间。

标签: c memory-management malloc


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    在我的专业代码中,我经常利用 malloc()[etc] 分配的actual size,而不是requested size。这是我确定actual分配大小0的函数:

    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】:

      简短的回答是标准的 malloc 接口不提供您正在寻找的信息。使用这些信息会破坏所提供的抽象。

      一些替代方案是:

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

      【讨论】:

        【解决方案4】:

        根据您对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,它可以在保证零拷贝的情况下扩大/缩小内存。它可能会移动您的虚拟机映射。这里唯一的问题是它只适用于系统页面大小的块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
          }
          

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-05-07
          • 2011-04-07
          • 2016-06-23
          • 2023-04-11
          • 2011-06-24
          • 1970-01-01
          • 2012-10-12
          • 1970-01-01
          相关资源
          最近更新 更多