2016-09-26 33 views
-3

我正在尝试读取C文件中的大型.txt文件。我用fgets()完成了一个版本,但性能受I/O限制。所以我需要别的可以比fgets()更好的性能,并且我发现mmap()不会被I/O所限制。所以我的问题是,是否有可能通过mmap()和多线程(POSIX Thread)来实现? 这里是我所需要的:用mmap读取文件中的多线程C

Different threads to read(mmap() or something else) different parts of the file simultaneously

我无法找到关于mmap()的多线程的任何资源网上,可能有人请帮助我做一些示例代码和解释?我会非常感谢你的帮助,感谢

+1

你想让不同的线程同时读取文件的不同部分? – yano

+1

你想阅读'\ n'分隔记录?如果这些行跨越页面边界会发生什么? – wildplasser

+0

@yano Yesss如你所说,有没有办法做到这一点? – superrman777

回答

0

而linux手册页mmap状态:

MMAP - 地图文件或设备到内存

#include <sys/mman.h> 
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); 

mmap描述说:

mmap()在虚拟地址spa中创建一个新的映射呼叫过程的ce。新映射的起始地址在addr中指定。 length参数指定映射的长度。

这里是手册页的代码示例。

#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); 
} 

这些都不是我的工作,全部来自Linux手册页。

+0

感谢您的答案,但我需要的是具有多线程的mmap – superrman777

1

你的想法本身并不差。如果我们假设有一个换行符分隔的文件(即:可以在没有斑点的行之间切换),可以找到类似块的块(从另一个程序中删除,因此请先检查)

// 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; 
} 

现在你可以开始一个线程,给它开始和结束它应该工作的块(你可能或可能没有用上面的函数计算),并且在单个线程内进行(m)映射而不用担心。如果输出与块的大小相同,甚至可以在原地工作。

编辑

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)确切数字)。没有什么问题,只需在开始之前将其设置到页面并在实际启动时开始工作,就可以获得所有必要的信息。您可以(并且必须!)忽略该页面的其余部分。

或者你把整个文件和映射它,并使用它,因为你会在驱动器上使用一个文件:给每个线程一块该地图(必要的信息在positions),并从那里工作。

第一个优点:您有几块内存可以被操作系统更容易地推动,并且您可能会或可能不会有多个CPU的缓存未命中。如果你运行一个集群或任何其他架构,每个CPU /一组CPU具有它自己的RAM或至少一个非常大的缓存,这是必须的。

后者的优点:更简单的实现,但你有一大块地图。这可能会或可能不会影响运行时间。

提示:我对现代快速固态硬盘的体验:现在的阅读速度非常高,您可以轻松地从直接文件访问开始,而非映射。即使使用相当慢的“普通”硬盘,也可以获得合理的速度。我从上面的代码片段中找到的程序必须搜索超过120 GB的大型CSV文件,并且没有足够的RAM来加载它,甚至没有足够的空间将其加载到某个数据库中(是的,那是几个多年前)。这是一个关键 - >“很多,不同的值”文件,幸好已经排序。所以我用上面的方法(KEY-> position)为它创建了一个小的(尽可能大的)驱动器索引文件,虽然比我的示例中的100多了更多的块。索引文件中的键也进行了排序,因此如果您搜索的键较大(数据按升序排序)比索引条目找到了正确的块,这意味着该键位于该块之前的块中如果存在的话。这些块足够小,可以将它们中的一部分保存在RAM中以作为缓存使用,但收益不大,传入请求相当均匀。

一个穷人的数据库可以这么说,而且速度足够快,可以在没有用户投诉的情况下完成这项工作。

有趣的一面是:键是字母数字,排序算法将它们排序为“aAbBcC ...”,这意味着您不能直接使用strcmp。让我挠了一阵头,但解决办法很简单:比较忽略大小写(例如:strcasecmp如果可用),如果它是不是等于 返回该结果,否则返回正常结果的相反strncmp(例如只是return -strcmp(a,b);)。

你对于需要处理的数据的种类非常沉默,所以上面可能没有丝毫兴趣。

+0

对不起,下一步有关mmap的是mmap(位置[i-> FILE_PARTS], chunk_size,PROT_READ,MAP_PRIVATE,fd,0)? – superrman777