簡體   English   中英

在 C 中使用 mmap 多線程讀取文件

[英]read file with mmap multiple threading in C

我正在嘗試用 C 讀取一個大的 .txt 文件。我已經用 fgets() 完成了一個版本,但性能受到 I/O 的限制。 所以我需要一些比 fgets() 性能更好的東西,而且我發現 mmap() 不會受到 I/O 的限制。 所以我的問題是,是否可以使用 mmap() 和多線程(POSIX 線程)來做到這一點? 這是我需要的:

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

我在網上找不到關於多線程 mmap() 的任何資源,有人可以幫我提供一些示例代碼並解釋一下嗎? 我將非常感謝您的幫助,謝謝

你的想法本身並不壞。 如果我們假設一個換行符分隔的文件(即:您可以在沒有問題的情況下在行之間進行剪切),您可以找到類似這樣的塊的 limtis(從我的另一個程序中刪除,所以請先檢查)

// 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 來完全加載它,驅動器上甚至沒有足夠的空間將其加載到某個數據庫中(是的,那是幾個幾年前)。 這是一個鍵->“很多、不同、不同的值”文件,幸運的是已經排序。 所以我用上面的方法(KEY->position)為它制作了一個小的(盡可能大的)索引文件,盡管比我的例子中的 100 塊多得多。 索引文件中的鍵也已排序,因此如果您要搜索的鍵比索引條目大(數據按升序排序),則您找到了正確的塊,這意味着該鍵位於之前的塊中位置(如果存在)。 這些塊足夠小,可以將它們中的一些保留在 RAM 中以用作緩存,但這並沒有增加多少,傳入的請求非常一致地隨機。

一個窮人的數據庫可以這么說,而且速度足夠快,可以在沒有用戶抱怨的情況下完成工作。

一個有趣的旁注:鍵是字母數字,排序算法將它們排序為“aAbBcC...”,這意味着您不能直接使用strcmp 讓我撓了一陣子,但解決方案相當簡單:比較忽略大小寫(例如: strcasecmp如果可用),如果相等,則返回該結果,否則返回與正常strncmp結果相反的結果(例如,只return -strcmp(a,b); )。

您對需要處理的數據類型保持沉默,因此您可能對上述內容沒有絲毫興趣。

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 手冊頁。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM