这个想法相对简单,但是我发现实现有些复杂,所以我想知道现在是否有可能.
>我想做的一个例子是在
缓冲区,然后将此缓冲区的内容映射到文件.代替
将内存空间虚拟地填充了
文件,我想传输原始缓冲区的内容
到系统缓存(应该是零复制操作),并且
立即弄脏(这会将数据刷新到磁盘
最终).
当然,我提到的问题是缓冲区应该被释放并取消映射(因为数据现在由系统缓存负责),我也不知道该怎么做.
重要方面是:
>该程序可以控制何时创建文件链接.
>不需要程序预测文件的大小,也不需要随着数据集的增长重新映射文件.相反,它可以重新分配初始缓冲区(为此使用高效的内存分配器),直到满意为止(它确定数据集将不再增长),然后再将其最终映射到文件.
>即使在将数据映射到文件后,仍可以通过相同的虚拟内存地址对其进行访问,但仍然没有单个内存内副本.
一种假设是:
>我们可以使用任意的内存分配器(或一般的内存管理方案)来管理动态缓冲区,而mmap / mremap可以更有效地管理动态缓冲区,因为后者必须管理文件系统以增大/缩小文件,这总是比较慢.
因此,(1)这些要求是否受到约束? (2)这个假设正确吗?
PS:我不得不随意选择这个问题的标签,但是我也很想听听BSD和Windows如何做到这一点.当然,如果POSIX API已经允许这样做,那就太好了.
更新:我称缓冲区为在主内存中分配的私有内存空间(任何具有正常VMM的操作系统中的进程/任务都是私有的).高层目标涉及使用另一个输入(在我的情况下为网络)生成任意大小的数据集,然后一旦生成,使其可长时间访问(对于网络和流程本身),然后保存到磁盘的过程中.
>如果我将数据集保存在私有内存中并正常写出,它们将仅在操作系统需要空间时才被交换,这有点愚蠢,因为它们已经在磁盘上了.
>如果我映射另一个区域,则必须将缓冲区的内容复制到该区域(位于系统缓存中),这又是一个愚蠢的选择,因为在此之后我将不再使用该缓冲区.
我看到的另一种选择是写入或使用成熟的userland缓存读取和写入磁盘本身,以确保(a)页面不会被无用地换出,并且(b)进程不会占用太多内存就其本身而言,这永远不可能实现最佳化(最好让内核来完成工作),而我认为这绝对不是一条值得走的路(太复杂而得不到收益).
更新:考虑到名义动物的答案,要求2和3不是问题.当然,这暗示该假设是不正确的,因为他证明了这种情况(开销很小).我还放宽了要求1,O_TMPFILE确实是完美的选择.
更新:A recent article on LWN在中间某处提到:“这可以通过实际上不会引起I / O的特殊写操作或将物理页传输到页缓存中的系统调用来完成”.这表明确实(至少2014年4月),至少对于Linux(以及可能的其他操作系统),目前尚无办法,而对标准API则更少.这篇文章是关于PostgreSQL的,但是所讨论的问题是相同的,除了可能对此问题的特定要求没有在本文中定义.
解决方法:
这不是对该问题的令人满意的答案;更像是评论链的延续.
这是一个测试程序,可以用来测量使用文件支持的内存映射而不是匿名内存映射的开销.
请注意,列出的work()函数仅用随机数据填充内存映射.为了更加现实,它应该至少模拟实际使用中预期的访问模式.
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <time.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* Xorshift random number generator.
*/
static uint32_t xorshift_state[4] = {
123456789U,
362436069U,
521288629U,
88675123U
};
static int xorshift_setseed(const void *const data, const size_t len)
{
uint32_t state[4] = { 0 };
if (len < 1)
return ENOENT;
else
if (len < sizeof state)
memcpy(state, data, len);
else
memcpy(state, data, sizeof state);
if (state[0] || state[1] || state[2] || state[3]) {
xorshift_state[0] = state[0];
xorshift_state[1] = state[1];
xorshift_state[2] = state[2];
xorshift_state[3] = state[3];
return 0;
}
return EINVAL;
}
static uint32_t xorshift_u32(void)
{
const uint32_t temp = xorshift_state[0] ^ (xorshift_state[0] << 11U);
xorshift_state[0] = xorshift_state[1];
xorshift_state[1] = xorshift_state[2];
xorshift_state[2] = xorshift_state[3];
return xorshift_state[3] ^= (temp >> 8U) ^ temp ^ (xorshift_state[3] >> 19U);
}
/* Wallclock timing functions.
*/
static struct timespec wallclock_started;
static void wallclock_start(void)
{
clock_gettime(CLOCK_REALTIME, &wallclock_started);
}
static double wallclock_stop(void)
{
struct timespec wallclock_stopped;
clock_gettime(CLOCK_REALTIME, &wallclock_stopped);
return difftime(wallclock_stopped.tv_sec, wallclock_started.tv_sec)
+ (double)(wallclock_stopped.tv_nsec - wallclock_started.tv_nsec) / 1000000000.0;
}
/* Accessor function. This needs to read/modify/write the mapping,
* simulating the actual work done onto the mapping.
*/
static void work(void *const area, size_t const length)
{
uint32_t *const data = (uint32_t *)area;
size_t size = length / sizeof data[0];
size_t i;
/* Add xorshift data. */
for (i = 0; i < size; i++)
data[i] += xorshift_u32();
}
int main(int argc, char *argv[])
{
long page, size, delta, maxsize, steps;
int fd, result;
void *map, *old;
char dummy;
double seconds;
page = sysconf(_SC_PAGESIZE);
if (argc < 5 || argc > 6 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s MAPFILE SIZE DELTA MAXSIZE [ SEEDSTRING ]\n", argv[0]);
fprintf(stderr, "Where:\n");
fprintf(stderr, " MAPFILE backing file, '-' for none\n");
fprintf(stderr, " SIZE initial map size\n");
fprintf(stderr, " DELTA map size change\n");
fprintf(stderr, " MAXSIZE final size of the map\n");
fprintf(stderr, " SEEDSTRING seeds the Xorshift PRNG\n");
fprintf(stderr, "Note: sizes must be page aligned, each page being %ld bytes.\n", (long)page);
fprintf(stderr, "\n");
return 1;
}
if (argc >= 6) {
if (xorshift_setseed(argv[5], strlen(argv[5]))) {
fprintf(stderr, "%s: Invalid seed string for the Xorshift generator.\n", argv[5]);
return 1;
} else {
fprintf(stderr, "Xorshift initialized with { %lu, %lu, %lu, %lu }.\n",
(unsigned long)xorshift_state[0],
(unsigned long)xorshift_state[1],
(unsigned long)xorshift_state[2],
(unsigned long)xorshift_state[3]);
fflush(stderr);
}
}
if (sscanf(argv[2], " %ld %c", &size, &dummy) != 1) {
fprintf(stderr, "%s: Invalid map size.\n", argv[2]);
return 1;
} else
if (size < page || size % page) {
fprintf(stderr, "%s: Map size must be a multiple of page size (%ld).\n", argv[2], page);
return 1;
}
if (sscanf(argv[3], " %ld %c", &delta, &dummy) != 1) {
fprintf(stderr, "%s: Invalid map size change.\n", argv[2]);
return 1;
} else
if (delta % page) {
fprintf(stderr, "%s: Map size change must be a multiple of page size (%ld).\n", argv[3], page);
return 1;
}
if (delta) {
if (sscanf(argv[4], " %ld %c", &maxsize, &dummy) != 1) {
fprintf(stderr, "%s: Invalid final map size.\n", argv[3]);
return 1;
} else
if (maxsize < page || maxsize % page) {
fprintf(stderr, "%s: Final map size must be a multiple of page size (%ld).\n", argv[4], page);
return 1;
}
steps = (maxsize - size) / delta;
if (steps < 0L)
steps = -steps;
} else {
maxsize = size;
steps = 0L;
}
/* Time measurement includes the file open etc. overheads.
*/
wallclock_start();
if (strlen(argv[1]) < 1 || !strcmp(argv[1], "-"))
fd = -1;
else {
do {
fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, 0600);
} while (fd == -1 && errno == EINTR);
if (fd == -1) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return 1;
}
do {
result = ftruncate(fd, (off_t)size);
} while (result == -1 && errno == EINTR);
if (result == -1) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
unlink(argv[1]);
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
return 1;
}
result = posix_fadvise(fd, 0, size, POSIX_FADV_RANDOM);
}
/* Initial mapping. */
if (fd == -1)
map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, fd, 0);
else
map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0);
if (map == MAP_FAILED) {
fprintf(stderr, "Memory map failed: %s.\n", strerror(errno));
if (fd != -1) {
unlink(argv[1]);
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
}
return 1;
}
result = posix_madvise(map, size, POSIX_MADV_RANDOM);
work(map, size);
while (steps-->0L) {
if (fd != -1) {
do {
result = ftruncate(fd, (off_t)(size + delta));
} while (result == -1 && errno == EINTR);
if (result == -1) {
fprintf(stderr, "%s: Cannot grow file: %s.\n", argv[1], strerror(errno));
unlink(argv[1]);
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
return 1;
}
result = posix_fadvise(fd, 0, size, POSIX_FADV_RANDOM);
}
old = map;
map = mremap(map, size, size + delta, MREMAP_MAYMOVE);
if (map == MAP_FAILED) {
fprintf(stderr, "Cannot remap memory map: %s.\n", strerror(errno));
munmap(old, size);
if (fd != -1) {
unlink(argv[1]);
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
}
return 1;
}
size += delta;
result = posix_madvise(map, size, POSIX_MADV_RANDOM);
work(map, size);
}
/* Timing does not include file renaming.
*/
seconds = wallclock_stop();
munmap(map, size);
if (fd != -1) {
unlink(argv[1]);
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
}
printf("%.9f seconds elapsed.\n", seconds);
return 0;
}
如果将以上内容另存为bench.c,则可以使用
gcc -W -Wall -O3 bench.c -lrt -o bench
在不带参数的情况下运行它以查看用法.
在我的机器上ext4文件系统上,运行测试
./bench - 4096 4096 4096000
./bench testfile 4096 4096 4096000
匿名存储映射的挂钟时间为1.307秒,文件支持的存储映射的时间为1.343秒,这意味着文件支持的映射要慢2.75%.
此测试从一页内存映射开始,然后将其放大一千倍.对于4096000 4096 8192000之类的测试,差异甚至更小.测量的时间确实包括构造初始文件(以及使用posix_fallocate()为文件分配磁盘上的块).
在同一台机器上的tmpfs,swRAID0上的ext4上和swRAID1上的ext4上运行测试似乎不会影响结果.所有的差异都消失了.
尽管我希望在发出任何清除语句之前先在多台计算机和内核版本上进行测试,但我确实了解内核如何管理这些内存映射.因此,基于以上和我的经验,我将提出以下主张:
与匿名内存映射相比,甚至与malloc()/ realloc()/ free()相比,使用文件支持的内存映射都不会导致明显的速度下降.我希望在所有实际使用案例中,差异不会超过5%,而在典型的实际使用案例中,差异最大为1%;如果调整大小与访问地图的频率相比很少,则调整的次数会更少.
对于user2266481来说,上面的意思是仅在目标文件系统上创建一个临时文件来保存内存映射应该是可以接受的. (请注意,可以在不允许任何人访问临时文件的情况下创建临时文件,方式0,因为仅在打开文件时才检查访问方式.)当内容为最终格式时,将ftruncate()和msync()内容,然后使用link()将最终文件硬链接到临时文件.最后,取消链接临时文件并关闭临时文件描述符,任务应以接近最佳的效率完成.