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

时间:2016-09-26 22:12:49

标签: c mmap

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

.wrapper { display: flex; flex-direction: row; flex-wrap: nowrap; } .box1 { background-color: beige; min-height: 300px; flex: 2 1 0; /* 2 - flex grow, 1 - flex shrink, 0 - flex basis */ } .box2 { background-color: cyan; min-height: 300px; flex: 1 0 0; }

我无法在网上找到任何关于mmap()的资源,有人可以帮我一些示例代码并解释一下吗?我非常感谢你的帮助,谢谢

2 个答案:

答案 0 :(得分: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或至少非常大的缓存,那么这是必须的。

后者的优点:实现起来比较简单,但你有一个大的地图丛。这可能会也可能不会影响运行时间。

提示:我对现代快速SSD的体验:如今阅读速度如此之高,您可以轻松地从直接文件访问开始而不是映射。即使使用相当慢的“普通”硬盘驱动器,您也可以获得合理的速度。我上面撕掉上面代码片段的程序必须搜索一个超过120 GB的大型CSV文件,没有足够的RAM来完全加载它,驱动器上甚至没有足够的空间将它加载到某个数据库中(是的,这是几个几年前)。这是一个关键 - &gt;“很多,不同,价值”的文件,谢天谢地已经排序了。所以我使用上面的方法(KEY-&gt; position)为它做了一个小的(我可以在驱动器上尽可能大)索引文件,虽然比我的例子中的100多了多少块。索引文件中的键也被排序,因此如果您搜索的键比索引条目更大(数据按升序排序),那么您找到了正确的块,这意味着键在此之前的块中位置,如果它存在。这些块足够小,可以将它们中的一些保留在RAM中作为缓存,但增加的不多,传入的请求非常随机。

一个穷人的数据库可以这样说,而且没有用户投诉就足够快地完成工作。

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

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

答案 1 :(得分:0)

mmap的linux手册页:

  

mmap - 将文件或设备映射到内存

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

mmap的说明:

  

mmap()在调用进程的虚拟地址空间中创建一个新映射。新映射的起始地址在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手册页。