【问题标题】:How to efficiently write a large sequence of NULL bytes in a file?如何有效地在文件中写入大量 NULL 字节?
【发布时间】:2013-11-19 08:04:05
【问题描述】:

我有一个文件描述符 fd、一个偏移量和一个长度,我需要从 offset 中写入 length NULL bytes fd 描述的文件(注意:它永远不会出现在文件的末尾)。

除了使用填充有NULLs 的缓冲区并在循环中重复写入之外,还有没有一种有效的方法来做到这一点? NULLs 的序列可能达到 16Mo,我目前使用大小为 512 的缓冲区(= ~30k 调用 write(2))。

【问题讨论】:

  • 你试过一些东西吗?
  • 你的缓冲区大小应该是底层文件系统块大小的倍数。例如如果您使用 4k 块,那么您应该使用 4k、8k、12k 等...缓冲区大小。
  • @MOHAMED,显然 OP 有,他使用 512 字节缓冲区并写入 n 次...
  • 与磁盘 I/O 相比,循环的开销是如此之小,以至于如果您对它进行基准测试,它甚至不会注册为开销。您正在尝试优化不需要优化的内容。
  • @Marc B:为什么?除非您调用fsync,否则内核不会将数据推送到磁盘中,除非您调用fsync

标签: c performance system-calls


【解决方案1】:

您可以尝试mmap以所需偏移量将文件映射到所需的大小,然后只需调用memset

编辑:根据@jthill 发布的代码,这是一个简单的示例,演示如何进行比较..

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void create(int fsize)
{
  FILE *fd = fopen("data", "wb");
  fseek(fd, fsize - 1, SEEK_SET);
  fputc(0, fd);
  fclose(fd);
}

void seek_write(const char* data, int wsize, int seek, int dsize)
{
  int fd = open("data", O_RDWR);
  // Now seek_write
  if (lseek(fd, seek, SEEK_SET) != seek)
    perror("seek?"), abort();
  // Now write in requested blocks..
  for (int c = dsize / wsize; c--;)
    if (write(fd, data, wsize) != wsize)
      perror("write?"), abort();
  close(fd);
}

void mmap_memset(int wsize, int seek, int dsize)
{
  int fd = open("data", O_RDWR);
  void* map = mmap(0, dsize + seek, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (map == MAP_FAILED)
    perror("mmap?"), abort();
  memset((char*)map + seek, 0, dsize);
  munmap(map, dsize);
  close(fd);
}

int main(int c, char **v)
{
  struct timeval start, end;
  long long ts1, ts2;
  int wsize = c>1 ? atoi(*++v) : 512;
  int seek  = c>2 ? atoi(*++v) : 0;
  int reps  = c>3 ? atoi(*++v) : 1000;
  int dsize = c>4 ? atoi(*++v) : 16*1024*1024;
  int fsize = c>5 ? atoi(*++v) : 32*1024*1024;

  // Create the file and grow...
  create(fsize);

  char *data = mmap(0, wsize, PROT_READ, MAP_ANON | MAP_PRIVATE, 0, 0);

  printf("Starting write...\n");
  gettimeofday(&start, NULL);
  for (int i = 0;i < reps; ++i)
    seek_write(data, wsize, seek, dsize);
  gettimeofday(&end, NULL);

  ts1 = ((end.tv_sec - start.tv_sec) * 1000000) + (end.tv_usec - start.tv_usec);

  printf("Starting mmap...\n");
  gettimeofday(&start, NULL);
  for (int i = 0;i < reps; ++i)
    mmap_memset(wsize, seek, dsize);
  gettimeofday(&end, NULL);

  ts2 = ((end.tv_sec - start.tv_sec) * 1000000) + (end.tv_usec - start.tv_usec);

  printf("write: %lld us, %f us\nmmap: %lld us, %f us", ts1, (double)ts1/reps, ts2, (double)ts2/reps);
}

注意:mmap 不喜欢如果提供的偏移量未对齐(通常在页面边界上),因此,如果您可以映射长度 + 偏移量并简单地从偏移量设置(或或者,如果你能保证一个很好地对齐的偏移量,这也将起作用..)

如您所见,这两个操作之间的区别是lseek (map + seek) 和write (memset)。我认为这是一个公平的比较(如果有人想解决任何问题,请随意。)

我也是用MAP_SHARED而不是MAP_PRIVATE,两者有很大区别,后者做copy-on-write,会慢很多!

在我不那么强大的系统上,我得到:

> ./fwrite 4096 1234
> Starting write...
> Starting mmap...
> write: 14767898 us, 14767.898000 us
> mmap: 6619623 us, 6619.623000 us

我认为这表明mmap + memset 更快?

【讨论】:

  • 这不可能比没有mmap 的标准写作方法快。
  • 在我看来它会慢得多,内核不必读取它知道将被擦除的任何块,并且它不知道使用 mmap 文件。跨度>
  • @jthill,@R,请检查更新,如果小微基准有什么要修复的,请告诉我..
【解决方案2】:

lseek() 到该位置,write() 一个大缓冲区一次(或一个较小的缓冲区多次)。

【讨论】:

    【解决方案3】:

    在 Linux 上,您可以使用 splice(2)/dev/zero 复制数据。

    这非常有效,因为大多数工作都在内核中完成。

    其他操作系统可能提供类似的功能(即sendfile)。

    更新

    我忘了fallocate(2) 可以在文件中间打孔。

    【讨论】:

    • 我会犹豫是否使用splicesendfile,即使它们有效,因为它们对使用的文件描述符的要求非常少(必须至少有一个 fd 到 splice管道?必须sendfile 写入套接字?等等),甚至可能因内核版本而异。
    • 你是对的 splice(2) 需要一个文件描述符作为管道。尽管该限制已被删除,因为我无法在手册页上找到它,但它只是埋在 ERRORS 部分中。
    【解决方案4】:

    如果您运行的是 Linux 并且文件系统支持稀疏文件,您可以尝试使用带有 FALLOC_FL_PUNCH_HOLE 标志的 fallocate(2) 在文件中打孔。尽管我没有测试它,但我希望它会很快。

    【讨论】:

    • 如果可用,这是最快的方式,但如果您打算随后在漏洞上写入数据,则可能会导致糟糕的碎片并最终导致非常糟糕的 IO 性能。
    • @R.. 是的。如果文件当前恰好跨越连续的块,并且他想稍后在归零的部分上写入,那可能是个坏主意。
    【解决方案5】:

    从下面看,16M 的 I/O 做得不好,只有一次,是 >20ms。这几乎是完全可以感知的。

    要点:

    • I/O 值得关注,因为
    • 做很多事情都会导致严重的延误。
    • 512 字节的写入很痛苦。
    • 4096 字节写入不会。

    内存或磁盘上的偏移量并不重要,所以calloc'ing 任何应该做的都很好(你可以试试)。

    #define _GNU_SOURCE
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    int main(int c, char **v)
    {
        int wsiz = c>1 ? atoi(*++v) : 512;
        int seek = c>2 ? atoi(*++v) : 0;
        int woff = c>3 ? atoi(*++v) : 0;
        int fsiz = c>4 ? atoi(*++v) : 16 * 1024 * 1024;
        int reps = c>5 ? atoi(*++v) : 1000;
    
        printf("writesize %d, seek  %d, align %d, filesize %d, reps %d\n",
               wsiz, seek, woff, fsiz, reps);
    
        if (wsiz<=0|seek<0|woff<0|fsiz<wsiz)
            return 1;
    
        int fd = open("data", O_CREAT | O_RDWR, 0700);
        char *data = mmap(0, 2*wsiz, PROT_READ, MAP_ANON | MAP_PRIVATE, 0, 0);
    
        for (int t = reps; t--; lseek(fd, seek, 0))
            for (int c = fsiz / wsiz; c--;)
                if (write(fd, data+woff, wsiz) != wsiz)
                    perror("write?"), abort();
    
        return close(fd);
    }
    
    cc  -o bin/wipetest -g -O   --std=c11 -march=native -pipe -Wall -Wno-parentheses    wipetest.c
    
    ------------------------------------------------------
    writesize 512, seek  0, align 0, filesize 16777216, reps 1000
    
    real    0m20.727s
    user    0m0.513s
    sys 0m20.220s
    ------------------------------------------------------
    writesize 4096, seek  0, align 0, filesize 16777216, reps 1000
    
    real    0m3.889s
    user    0m0.077s
    sys 0m3.687s
    ------------------------------------------------------
    writesize 16777216, seek  0, align 0, filesize 16777216, reps 1000
    
    real    0m3.205s
    user    0m0.000s
    sys 0m3.203s
    ------------------------------------------------------
    writesize 512, seek  500, align 0, filesize 16777216, reps 1000
    
    real    0m23.829s
    user    0m0.463s
    sys 0m23.247s
    ------------------------------------------------------
    writesize 4096, seek  500, align 0, filesize 16777216, reps 1000
    
    real    0m5.531s
    user    0m0.053s
    sys 0m5.480s
    ------------------------------------------------------
    writesize 16777216, seek  500, align 0, filesize 16777216, reps 1000
    
    real    0m3.435s
    user    0m0.000s
    sys 0m3.433s
    ------------------------------------------------------
    writesize 512, seek  0, align 12, filesize 16777216, reps 1000
    
    real    0m21.478s
    user    0m0.537s
    sys 0m20.820s
    ------------------------------------------------------
    writesize 4096, seek  0, align 12, filesize 16777216, reps 1000
    
    real    0m3.722s
    user    0m0.057s
    sys 0m3.667s
    ------------------------------------------------------
    writesize 16777216, seek  0, align 12, filesize 16777216, reps 1000
    
    real    0m3.232s
    user    0m0.000s
    sys 0m3.233s
    ------------------------------------------------------
    writesize 512, seek  500, align 12, filesize 16777216, reps 1000
    
    real    0m23.775s
    user    0m0.550s
    sys 0m23.113s
    ------------------------------------------------------
    writesize 4096, seek  500, align 12, filesize 16777216, reps 1000
    
    real    0m5.566s
    user    0m0.050s
    sys 0m5.517s
    ------------------------------------------------------
    writesize 16777216, seek  500, align 12, filesize 16777216, reps 1000
    
    real    0m3.277s
    user    0m0.000s
    sys 0m3.277s
    

    【讨论】:

      猜你喜欢
      • 2018-07-08
      • 2019-12-21
      • 2019-08-02
      • 1970-01-01
      • 2014-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-01
      相关资源
      最近更新 更多