在C中使用mmap多线程读取文件
我正在尝试用C读取一个大的.txt文件。我已经用fgets()做了一个版本,但是性能受到I/O的限制。所以我需要一些比fgets()性能更好的东西,我发现mmap()不会受到I/O的限制。所以我的问题是,用mmap()和多线程(POSIX线程)可以做到这一点吗? 我需要的是:在C中使用mmap多线程读取文件,c,mmap,C,Mmap,我正在尝试用C读取一个大的.txt文件。我已经用fgets()做了一个版本,但是性能受到I/O的限制。所以我需要一些比fgets()性能更好的东西,我发现mmap()不会受到I/O的限制。所以我的问题是,用mmap()和多线程(POSIX线程)可以做到这一点吗? 我需要的是: 同时读取(mmap()或其他内容)文件不同部分的不同线程 我在网上找不到任何关于多线程mmap()的资源,有人能帮我提供一些示例代码并解释一下吗?非常感谢您的帮助,谢谢mmap的linux手册页面说明: mmap-将文件
同时读取(mmap()或其他内容)文件不同部分的不同线程
我在网上找不到任何关于多线程mmap()的资源,有人能帮我提供一些示例代码并解释一下吗?非常感谢您的帮助,谢谢mmap的linux手册页面说明: mmap-将文件或设备映射到内存中
这些都不是我的工作,都是从Linux手册页上获得的。你的想法本身也不错。如果我们假设一个换行分隔的文件(也就是说:你可以在两行之间剪切而不出现问题),你可以找到类似的块的边缘(从我的另一个程序中删除,所以请先检查) 如果您不关心特定地址,请将其设置为
NULL
length
是您希望将映射初始化为的字节数,即在本例中:由文件描述符fd
中的内容填充填充的开始由
offset
设置,但有一个令人不安的警告:它需要是页面大小的倍数(询问sysconf(\u SC\u page\u size)
,了解确切的数字)。问题不大,只需将其设置为开始前的页面,并在实际开始时开始工作,所有必要的信息都存在。你可以(而且必须!)忽略该页面的其余部分
或者将整个文件映射并像使用驱动器上的文件一样使用它:给每个线程一个映射块(位于位置的必要信息),然后从那里开始工作
第一种方法的优点是:您有几个内存块,操作系统可以更容易地将它们放在一起,多个CPU的缓存未命中率可能会更低,也可能不会更低。如果您运行一个集群或任何其他体系结构,其中每个CPU/CPU组都有自己的RAM,或者至少有一个非常大的缓存,那么这也是必须的
后者的优点:实现起来更简单,但您有一个大的地图束。这可能会也可能不会影响运行时
提示:我对现代、快速SSD的体验:这些天的读取速度如此之高,您可以轻松地从直接文件访问开始,而不是从映射开始。即使使用相当慢的“普通”硬盘,您也可以获得合理的速度。我从上面撕下这段代码的程序必须搜索一个超过120 GB的大型CSV文件,没有足够的RAM来完全加载它,驱动器上甚至没有足够的空间将它加载到一些DB中(是的,那是几年前的事了)。这是一个key->“lot,of,different,values”文件,谢天谢地,它已经排序好了。因此,我用上面的方法(KEY->position)为它创建了一个小的(尽可能大的)索引文件,虽然比我的示例中的100块要多。索引文件中的键也已排序,因此如果要搜索的键比索引项大(数据按升序排序),则找到了正确的块,这意味着该键位于该位置(如果存在)之前的块中。这些块足够小,可以将它们中的一些保存在RAM中作为缓存使用,但这并没有带来多少好处,传入的请求是完全随机的
可以说,这是一个穷人的数据库,而且速度足够快,可以在没有用户抱怨的情况下完成这项工作
有趣的一点是:键是字母数字的,排序算法将它们排序为“aAbBcC…”,这意味着您不能直接使用strcmp。让我挠头了一段时间,但解决办法相当简单:比较忽略大小写(例如:strcasecmp
,如果有),如果大小写不相等
返回该结果,否则返回与正常的strncmp结果相反的结果(例如justreturn-strcmp(a,b);
)
您对需要处理的数据类型非常沉默,因此您可能对上面的内容一点也不感兴趣。是否希望不同的线程同时读取文件的不同部分?是否要读取“\n”分隔的记录?如果行跨越页面边界会发生什么情况?@yano Yesss正如您所说,有什么方法可以做到这一点吗?我会在主线程中执行mmap
,然后将每个工作线程指定为以特定偏移量读取文件的一部分。每个线程将负责阅读它们的部分,它们不会互相践踏。在细节上可能会有点头疼,特别是如果您确定在运行时生成多少个线程以及每个线程的数据部分的大小。mmap()不会受到I/O的限制
现在您为什么会这样想?磁盘上的数据必须进入CPU——这是我/其他人的答案,但我需要的是带有多线程的mmap,所以关于mmap的下一步是mmap(位置[I->FILE\u PARTS],块大小,保护读取,映射私有,fd,0)?
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define handle_error(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)
int main(int argc, char *argv[])
{
char *addr;
int fd;
struct stat sb;
off_t offset, pa_offset;
size_t length;
ssize_t s;
if (argc < 3 || argc > 4) {
fprintf(stderr, "%s file offset [length]\n", argv[0]);
exit(EXIT_FAILURE);
}
fd = open(argv[1], O_RDONLY);
if (fd == -1)
handle_error("open");
if (fstat(fd, &sb) == -1) /* To obtain file size */
handle_error("fstat");
offset = atoi(argv[2]);
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
/* offset for mmap() must be page aligned */
if (offset >= sb.st_size) {
fprintf(stderr, "offset is past end of file\n");
exit(EXIT_FAILURE);
}
if (argc == 4) {
length = atoi(argv[3]);
if (offset + length > sb.st_size)
length = sb.st_size - offset;
} else { /* No length arg ==> display to end of file */
length = sb.st_size - offset;
}
addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
MAP_PRIVATE, fd, pa_offset);
if (addr == MAP_FAILED)
handle_error("mmap");
s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
if (s != length) {
if (s == -1)
handle_error("write");
fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
// 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;
}
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);