全页Malloc

时间:2013-02-21 15:45:15

标签: c memory malloc page-size

我正在尝试一次使用整个页面来优化程序的内存分配。

我抓住这样的页面大小:sysconf(_SC_PAGESIZE);然后计算适合页面的元素总数:elements=pageSize/sizeof(Node);

我的想法是,当我真正去找我的记忆时,我会使用malloc(elements*sizeof(Node));似乎sifeof(Node)的乘法和除法会抵消,但是对于整数除法,我不相信是这样的。

这是一次malloc整个页面的最佳方式吗?

由于

5 个答案:

答案 0 :(得分:5)

您的计算elements=pageSize/sizeof(Node);没有考虑添加到malloc()返回的任何块/内存块的malloc()元数据。在许多情况下,malloc()将返回一个可能至少在min(sizeof(double),2 * sizeof(void *))边界上对齐的内存块(32字节变得非常普遍......)。如果malloc()在页面上对齐内存块,添加其块(使用填充),并且您编写整页大小的数据,则最后一个字节不在第一页:所以您最终使用2页。

想要整页,只为你而不用担心浪费内存,而不是按照评论中的建议使用mmap() / VirtualAlloc()? 你在这里:

int ret;
void *ptr = NULL;
size_t page_size = sysconf(_SC_PAGESIZE);

ret = posix_memalign(&ptr, page_size, page_size);
if (ret != 0 || ptr == NULL) {
    fprintf(stderr, "posix_memalign failed: %s\n", strerror(ret));
}

顺便说一句,这可能与微观优化有关。 您可能仍未检查Node是否具有缓存行的大小倍数,也未检查如何改进缓存局部性,也未找到减少内存碎片的方法。因此,您可能会采用错误的方式:首先使其工作,使用profil,优化算法,profil,在最后一个选项进行微优化。

答案 1 :(得分:4)

malloc函数没有pagesize的任何概念。除非您正在分配与页面边界对齐的页面,否则以这种方式调用malloc将无法获得任何好处。只需malloc您需要的元素,并且不再担心微观优化,几乎肯定不会给您带来任何好处。

是的,Linux内核一直都是这样做的。这有两个原因:

  1. 您不希望分配比页面更大的块,因为这会显着增加分配失败的风险。
  2. 内核分配是基于每页进行的,而不是像C库一样,它一次分配大量内存,然后将其拆分为小组件。
  3. 如果您确实要分配页面大小的内存量,请使用sysconf(_SC_PAGESIZE)的结果作为大小参数。但几乎可以肯定,你的分配跨越了两页。

答案 2 :(得分:1)

标准不保证malloc甚至有页面大小的概念。但是,当请求的分配大小在页面大小(或更大)的顺序上时,malloc实现发布整个页面的情况并不少见。

在要求分配恰好等于页面大小(或页面大小的倍数)并自行细分它当然没有害处,尽管这是一项额外的工作。您可能确实得到了您想要的行为,至少在某些机器/编译器/库组合上。但你可能也不会。如果您绝对需要页面大小的分配和/​​或页面对齐的内存,则必须调用特定于操作系统的API来获取它。

答案 3 :(得分:1)

C11标准添加了aligned_alloc调用,因此您可以执行以下操作:

#include <stdlib.h>
#include <unistd.h>

void *alloc_page( void )
{
    long page_size = sysconf( _SC_PAGESIZE );  /* arguably could be a constant, #define, etc. */

    return ( page_size > 0 ? aligned_alloc( page_size, page_size ) : NULL );
}

正如其他人所指出的,这种方法的问题在于,通常标准alloc调用的实现会增加一些记录开销,这些开销存储在分配的内存之前。因此,这种分配通常会跨越两个页面:您要使用的返回页面,以及分配器簿记使用的另一个页面的最后一页。

这意味着当您释放或重新分配此内存时,可能需要触摸两个页面而不仅仅是一个页面。此外,如果你以这种方式分配全部或大部分记忆,那么你可以浪费&#34;大量的虚拟内存,因为在操作系统级别分配给您的进程的页面大约只有一半用于分配器的簿记。

这些问题的重要性一般很难说,但最好以某种方式避免这些问题。不幸的是,我还没有想出一个干净,简单,便携的方法。

==============================

附录:如果你可以动态地计算出malloc的内存开销并假设它总是不变的,那么会要求更少的东西通常给我们想要的东西吗?

#include <stdlib.h>
#include <unistd.h>

/* decent default guesses (e.g. - Linux x64) */

static size_t Page_Size       = 4096;
static size_t Malloc_Overhead = 32;

/* call once at beginning of program (i.e. - single thread, no allocs yet) */

int alloc_page_init( void )  
{
    int     ret       = -1;
    long    page_size = sysconf( _SC_PAGESIZE );
    char   *p1        = malloc( 1 );
    char   *p2        = malloc( 1 );
    size_t  malloc_overhead;

    if ( page_size <= 0 || p1 == NULL || p2 == NULL )
        goto FAIL;

    malloc_overhead = ( size_t ) ( p2 > p1 ? p2 - p1 : p1 - p2 );  /* non-standard pointer math */

    if ( malloc_overhead > 64 || malloc_overhead >= page_size )
        goto FAIL;

    Page_Size       = page_size;
    Malloc_Overhead = malloc_overhead;
    ret             = 0;

FAIL:
    if ( p1 )
        free( p1 );

    if ( p2 )
        free( p2 );

    return ret;
}

void *alloc_page( void )
{
    return aligned_alloc( Page_Size - Malloc_Overhead, Page_Size - Malloc_Overhead );
}

答案:可能不是,因为,例如,&#34;作为实施支持的一个例子&#34;要求,POSIX函数posix_memalign接受任意对齐,该对齐是2的幂和sizeof(void *)的倍数,并且aligned_alloc的基于POSIX的实现继承这些要求。&#34;

上述代码可能不会请求2的幂的对齐,因此可能在大多数平台上失败。

对于标准分配函数的典型实现来说,这似乎是一个不可避免的问题。因此,最好根据页面大小进行对齐和分配,并可能支付驻留在另一个页面上的分配器簿记的惩罚,或使用特定于操作系统的调用(如mmap)来避免此问题。

答案 4 :(得分:1)

如果您的问题是关于如何分配整个内存页面:请使用mmap(),而不是malloc()
原因:
malloc()必须始终为每个分配添加一些元数据,因此如果您执行malloc(4096),它肯定会分配多个页面。另一方面,mmap()是用于将网页映射到地址空间的内核 API。这是malloc()在幕后使用的内容。

如果您的问题是关于正确的舍入:将a舍入到N的倍数的常用技巧是rounded = (a + N-1)/N*N;。首先添加N-1,确保在所有情况下该部门都会向上舍入。如果a已经是N的倍数,则添加的N-1将无效;在所有其他情况下,您获得的不仅仅是rounded = a/N*N;