gpt4 book ai didi

c - C 中使用 mmap 多线程读取文件

转载 作者:行者123 更新时间:2023-11-30 20:22:21 24 4
gpt4 key购买 nike

我正在尝试用 C 语言读取一个大的 .txt 文件。我已经使用 fgets() 完成了一个版本,但性能受到 I/O 的限制。所以我需要其他比 fgets() 更好的性能,而且我发现 mmap() 不会受到 I/O 的限制。所以我的问题是,是否可以使用 mmap() 和多线程(POSIX 线程)来做到这一点?这就是我需要的:

不同的线程同时读取(mmap() 或其他)文件的不同部分

我在网上找不到任何关于带有多线程的 mmap() 的资源,有人可以帮我提供一些示例代码并解释一下吗?我将非常感谢您的帮助,谢谢

最佳答案

你的想法本身还不错。如果我们假设一个换行符分隔文件(即:您可以在没有问题的情况下在行之间进行剪切),您可以找到具有类似内容的 block 的边界(从我的另一个程序中删除,因此请先检查)

// just in case
#define _LARGEFILE_SOURCE
#define _BSD_SOURCE
#define _POSIX_C_SOURCE 200112L

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

// TODO: should be calculated
#define FILE_PARTS 100
// TODO: should not be global
off_t positions[FILE_PARTS + 1];

int slice_file(FILE * fp)
{
off_t curr_pos = 0;
off_t filesize = 0;
off_t chunk_size = 0;
int fd;
int i, res;
char c;

struct stat sb;

// get size of file
fd = fileno(fp);
if (fd == -1) {
fprintf(stderr, "EBADF in prepare_and_backup() for data-file pointer\n");
return 0;
}

if (fstat(fd, &sb) == -1) {
fprintf(stderr, "fstat() failed\n");
return 0;
}
// check if it is a regular file
if ((sb.st_mode & S_IFMT) != S_IFREG) {
fprintf(stderr, "Not a regular file\n");
return 0;
}
// TODO: check if filesize and chunksize >> 1
filesize = sb.st_size;
chunk_size = filesize / ((off_t) FILE_PARTS);

positions[0] = 0;
curr_pos = 0;

for (i = 1; i < FILE_PARTS; i++) {
res = fseeko(fp, curr_pos, SEEK_SET);
if (res == -1) {
fprintf(stderr, "Error in fseeko(): %s\n",
strerror(errno));
return 0;
}
curr_pos += chunk_size;
// look for the end of the line to cut at useful places
while ((c = fgetc(fp)) != EOF) {
curr_pos++;
// TODO: add code to honor Apple's special needs
if (c == '\n') {
c = fgetc(fp);
if (c == EOF) {
break;
}
curr_pos++;
break;
}
}
positions[i] = curr_pos - 1;
}
// Position of the end of the file
positions[i] = filesize;
// Is that even needed?
rewind(fp);
return 1;
}

现在你可以启动一个线程,给它指定它应该工作的 block 的开始和结束(你可能已经或可能没有使用上面的函数计算)并在各个线程内进行(m)映射而无需担心。如果输出与 block 的大小相同,您甚至可以就地工作。

编辑

mmap 的声明是

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

如果您不关心特定地址,请将其设置为NULL
length 是您希望映射初始化的字节数,在本例中:用文件描述符 fd 中的内容填充。
填充的开始由 offset 设置,但有一个令人不安的警告:它需要是页面大小的倍数(询问 sysconf(_SC_PAGE_SIZE) 以获得确切的数字)。问题不大,只需将其设置为开始之前的页面并在实际开始时开始工作即可,所有必要的信息都存在。您可以(并且必须!)忽略该页面的其余部分。

或者您获取整个文件并映射它并像使用驱动器上的文件一样使用它:为每个线程提供该映射的一个 block (位置中的必要信息)并从那里开始工作.

第一个的优点:您拥有多个内存块,操作系统可以更轻松地移动这些内存块,并且使用多个 CPU 时,您可能会或可能不会减少缓存未命中的情况。如果您运行一个集群或任何其他架构,其中每个 CPU/CPU 组都有自己的 RAM 或至少一个非常大的缓存,那么它甚至是必须的。

后者的优点:实现起来更简单,但你有一大堆 map 。这可能会也可能不会影响运行时间。

提示:我对现代快速 SSD 的体验:现在的读取速度非常高,您可以轻松地从直接文件访问而不是映射开始。即使使用相当慢的“普通”硬盘,您也可以获得合理的速度。我从中提取上面代码片段的程序必须搜索超过 120 GB 的大型 CSV 文件,没有足够的 RAM 来完全加载它,甚至驱动器上没有足够的空间来将其加载到某个数据库中(是的,这是几个几年前)。这是一个键->“很多不同的值”文件,幸运的是已经排序了。因此,我使用上面的方法(KEY->position)为其制作了一个小的(与驱动器上可以容纳的一样大)索引文件,尽管比我的示例中的 100 个 block 多得多。索引文件中的键也已排序,因此如果您要搜索的键比索引条目更大(数据按升序排序),则您找到了正确的 block ,这意味着键位于之前的 block 中位置(如果存在)。这些 block 足够小,可以将其中一些 block 保留在 RAM 中作为缓存,但获得的效果并不多,传入的请求非常均匀地随机。

可以说是穷人的数据库,并且速度足够快,可以完成工作而不会受到用户的提示。

一个有趣的旁注:键是字母数字的,排序算法将它们排序为“aAbBcC ...”,这意味着您不能直接使用 strcmp 。让我摸不着头脑,但解决方案相当简单:比较忽略大小写(例如:strcasecmp,如果可用)和它是否相等 返回该结果,否则返回与正常 strncmp 结果相反的结果(例如,仅 return -strcmp(a,b);)。

您对需要处理的数据类型保持沉默,因此您可能对以上内容没有丝毫兴趣。

关于c - C 中使用 mmap 多线程读取文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39713223/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com