mmap 对不同的文件大小执行不同的操作
mmap perfoming different with different file sizes
我正在使用 mmap 读取文件。现在,当文件大小约为 880 MB 时,遍历文件大约需要 0.5 秒。
现在我通过复制文件内容将文件大小增加了 10 倍。现在再次使用 mmap 大约需要 1 分钟。
我认为迭代时间应该随文件大小线性增加。
这是简单的测试代码
FILE *fp = fopen64("filename", "rm");
if (fp == NULL)
{
perror(NULL);
}
struct stat st;
fstat(fileno(fp), &st);
off_t fileSize = st.st_size;
char *data = (char *)mmap64(NULL, fileSize, PROT_READ,MAP_PRIVATE,fileno(fp), 0);
off_t ptr =0;
char c =(char)0;
while(ptr < fileSize) { c += data[ptr++] ;}
std::cout << c << "\n";
这是结果。
文件大小为 880MB
real 0m0.501s
user 0m0.447s
sys 0m0.053s
文件大小为 8.8 GB
real 0m57.685s
user 0m10.773s
sys 0m3.690s
访问时间不是恒定的,数据集越大访问时间越慢。我建议阅读 Latency Numbers Every Programmer Should Know.
如果您 运行 一个基准并调整数据集大小,您将看到性能变化的几个范围。
当数据集适合 L1 缓存时,性能最快。 L1 缓存很小,比如每个内核 64 KiB,但速度很快(~1 个周期访问时间,几乎与寄存器一样快)。
需要二级缓存时性能突然下降。 L2 缓存比 L1 缓存更大且更慢。性能下降大约 10 倍。
当您的数据集对于 L2 缓存来说太大但适合 RAM 时,性能再次下降。缓存未命中的性能又下降了 10 倍左右。
当您的数据集对于 RAM 来说太大但适合磁盘时,性能会下降。假设你有一个快速的 SSD,性能损失大约是缓存未命中的 1000 倍,如果你有一个非 SSD 硬盘驱动器,性能损失可能是 100,000 倍。
您的 880 MB 数据集完全适合 8 GiB RAM,但 8,800 MB 数据集不适合,它不能同时驻留。随机访问模式有点悲观,但即使使用线性访问模式,您的页面也会从缓存中逐出,内核将不得不一遍又一遍地从磁盘读取它们。
很高兴假装您拥有无限量的存储,而且速度都相同,但事实并非如此。
红鲱鱼
实际上,将文件存入内存的唯一两种方法是 read
或 mmap
。其他选项只是这两个选项之上的层。对于不在页面缓存中的数据的顺序访问,read
和 mmap
之间的区别无关紧要,请参阅 mmap() vs. reading blocks
访问模式会改变数据集变大时性能下降的程度,但不会改变数据集太大而无法驻留不能比磁盘快的事实。
小注
如果你要mmap
那么使用open
而不是fopen
,fopen
是不必要的。
fopen
的 "m"
标志与您认为的不同,它在这里没有任何作用。
不要使用 open64
、fopen64
、mmap64
或任何废话。只需使用 #define _FILE_OFFSET_BITS 64
。这是现代的做事方式,但当然,它仅与 32 位系统相关——并且由于您在偏移量零处使用 mmap
,所以没有意义。
调用 perror
但继续是错误的。 err()
函数并非普遍可用,但可以满足您的需求。
没有充分的理由不在这里使用 MAP_SHARED
,但它不会改变任何东西。
下面是经过更一致的错误检查后的代码:
int fp = open("filename", O_RDONLY);
if (fp == -1)
err(1, "open");
struct stat st;
int r = fstat(fp, &st);
if (r == -1)
err(1, "stat");
// Compiler warning on 64-bit, but is correct
if (st.st_size > (size_t)-1)
errx(1, "file too large");
size_t sz = st.st_size;
void *data = mmap(NULL, sz, PROT_READ, MAP_SHARED, fp, 0);
if (data == MAP_FAILED)
err(1, "mmap");
unsigned counter = 0;
for (char *ptr = data, end = ptr + sz; ptr != end; ptr++)
counter += *ptr;
printf("%u\n", counter);
我正在使用 mmap 读取文件。现在,当文件大小约为 880 MB 时,遍历文件大约需要 0.5 秒。
现在我通过复制文件内容将文件大小增加了 10 倍。现在再次使用 mmap 大约需要 1 分钟。
我认为迭代时间应该随文件大小线性增加。
这是简单的测试代码
FILE *fp = fopen64("filename", "rm");
if (fp == NULL)
{
perror(NULL);
}
struct stat st;
fstat(fileno(fp), &st);
off_t fileSize = st.st_size;
char *data = (char *)mmap64(NULL, fileSize, PROT_READ,MAP_PRIVATE,fileno(fp), 0);
off_t ptr =0;
char c =(char)0;
while(ptr < fileSize) { c += data[ptr++] ;}
std::cout << c << "\n";
这是结果。
文件大小为 880MB
real 0m0.501s
user 0m0.447s
sys 0m0.053s
文件大小为 8.8 GB
real 0m57.685s
user 0m10.773s
sys 0m3.690s
访问时间不是恒定的,数据集越大访问时间越慢。我建议阅读 Latency Numbers Every Programmer Should Know.
如果您 运行 一个基准并调整数据集大小,您将看到性能变化的几个范围。
当数据集适合 L1 缓存时,性能最快。 L1 缓存很小,比如每个内核 64 KiB,但速度很快(~1 个周期访问时间,几乎与寄存器一样快)。
需要二级缓存时性能突然下降。 L2 缓存比 L1 缓存更大且更慢。性能下降大约 10 倍。
当您的数据集对于 L2 缓存来说太大但适合 RAM 时,性能再次下降。缓存未命中的性能又下降了 10 倍左右。
当您的数据集对于 RAM 来说太大但适合磁盘时,性能会下降。假设你有一个快速的 SSD,性能损失大约是缓存未命中的 1000 倍,如果你有一个非 SSD 硬盘驱动器,性能损失可能是 100,000 倍。
您的 880 MB 数据集完全适合 8 GiB RAM,但 8,800 MB 数据集不适合,它不能同时驻留。随机访问模式有点悲观,但即使使用线性访问模式,您的页面也会从缓存中逐出,内核将不得不一遍又一遍地从磁盘读取它们。
很高兴假装您拥有无限量的存储,而且速度都相同,但事实并非如此。
红鲱鱼
实际上,将文件存入内存的唯一两种方法是
read
或mmap
。其他选项只是这两个选项之上的层。对于不在页面缓存中的数据的顺序访问,read
和mmap
之间的区别无关紧要,请参阅 mmap() vs. reading blocks访问模式会改变数据集变大时性能下降的程度,但不会改变数据集太大而无法驻留不能比磁盘快的事实。
小注
如果你要
mmap
那么使用open
而不是fopen
,fopen
是不必要的。fopen
的"m"
标志与您认为的不同,它在这里没有任何作用。不要使用
open64
、fopen64
、mmap64
或任何废话。只需使用#define _FILE_OFFSET_BITS 64
。这是现代的做事方式,但当然,它仅与 32 位系统相关——并且由于您在偏移量零处使用mmap
,所以没有意义。调用
perror
但继续是错误的。err()
函数并非普遍可用,但可以满足您的需求。没有充分的理由不在这里使用
MAP_SHARED
,但它不会改变任何东西。
下面是经过更一致的错误检查后的代码:
int fp = open("filename", O_RDONLY);
if (fp == -1)
err(1, "open");
struct stat st;
int r = fstat(fp, &st);
if (r == -1)
err(1, "stat");
// Compiler warning on 64-bit, but is correct
if (st.st_size > (size_t)-1)
errx(1, "file too large");
size_t sz = st.st_size;
void *data = mmap(NULL, sz, PROT_READ, MAP_SHARED, fp, 0);
if (data == MAP_FAILED)
err(1, "mmap");
unsigned counter = 0;
for (char *ptr = data, end = ptr + sz; ptr != end; ptr++)
counter += *ptr;
printf("%u\n", counter);