我试图通过一次使用整个页面来优化我的程序的内存分配。
我正在像这样抓取页面大小:sysconf(_SC_PAGESIZE);
然后计算适合这样页面的元素总数:elements=pageSize/sizeof(Node);
我在想,当我真正去 malloc 我的记忆时,我会使用malloc(elements*sizeof(Node));
它似乎 sifeof(Node) 的乘法和除法会抵消,但是对于整数除法,我不认为是这种情况。
这是一次 malloc 整个页面的最佳方式吗?
谢谢
该malloc
函数没有任何页面大小的概念。除非您分配的页面也与页面边界对齐,否则您不会从malloc
以这种方式调用中获得任何好处。尽可能多的元素,不要malloc
再担心微优化几乎肯定不会给你带来任何好处的东西。
是的,Linux 内核一直在做这样的事情。有两个原因:
如果您真的想分配页面大小的内存量,请使用结果sysconf(_SC_PAGESIZE)
作为您的大小参数。但几乎可以肯定,您的分配跨越了两页。
您的计算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
的缓存线的大小倍数,也没有检查如何提高缓存局部性,也没有找到减少内存碎片的方法。所以你可能走错了路:首先让它工作,分析,优化你的算法,分析,最后一个选项进行微优化。
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 );
}
正如其他人指出的那样,这种方法的问题在于,标准分配调用的实现通常会增加一些记账开销,这些开销存储在分配的内存之前。因此,这种分配通常会跨越两页:返回的页面供您使用,以及分配器簿记使用的另一页的最后。
这意味着当您释放或重新分配此内存时,它可能需要触摸两页而不仅仅是一页。此外,如果您以这种方式分配全部或大部分内存,那么您可能会“浪费”大量虚拟内存,因为在操作系统级别分配给您的进程的大约一半页面只会用于分配器的簿记中的一小部分.
通常很难说这些问题有多重要,但最好以某种方式避免它们。不幸的是,我还没有想出一个干净、简单、便携的方法来做到这一点。
===============================
附录:如果您可以动态计算出 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 );
}
回答:可能不是,因为例如,“作为“由实现支持”要求的示例,POSIX 函数 posix_memalign 接受任何对齐方式,它是 2 的幂和 sizeof(void *) 的倍数,并且基于 POSIX aligned_alloc 的实现继承了这些要求。”
上面的代码可能不会要求对齐是 2 的幂,因此在大多数平台上可能会失败。
对于标准分配函数的典型实现来说,这似乎是一个不可避免的问题。因此,最好只根据页面大小对齐和分配,并可能支付分配器记账驻留在另一个页面上的代价,或者使用诸如 mmap 之类的操作系统特定调用来避免此问题。
malloc
该标准甚至没有提供页面大小概念的保证。但是,当请求的分配大小为页面大小(或更大)的数量级时,malloc 实现分配整个页面的情况并不少见。
要求分配恰好等于页面大小(或页面大小的倍数)并自己细分它当然没有什么害处,尽管这是一些额外的工作。至少在某些机器/编译器/库组合上,您可能确实得到了您想要的行为。但你也可能不会。如果您绝对需要页面大小的分配和/或页面对齐的内存,则必须调用特定于操作系统的 API 来获取它。
如果您的问题是关于如何分配整个内存页面:使用mmap()
,而不是malloc()
.
原因:
malloc()
必须总是在每次分配中添加一些元数据,所以如果你这样做malloc(4096)
肯定会分配不止一个页面。mmap()
另一方面,是内核的API,用于将页面映射到您的地址空间。这是malloc()
在引擎盖下使用的东西。
如果您的问题是关于正确的舍入:四舍五入a
到倍数的常用技巧N
是说rounded = (a + N-1)/N*N;
. 通过N-1
首先添加,您可以确保除法在所有情况下都会四舍五入。如果a
已经是 的倍数N
,则相加N-1
无效;在所有其他情况下,你得到的比 with 多一个rounded = a/N*N;
。