如何有效地在文件中写入大量的NULL字节序列?

bfo*_*ine 7 c performance system-calls

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

除了使用填充了NULLs 的缓冲区并重复将其写入循环之外,是否有一种有效的方法可以做到这一点?NULLs 的序列可以达到16Mo,我目前使用512的缓冲区(= ~30k调用write(2)).

Nim*_*Nim 3

您可以尝试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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

我也使用MAP_SHARED而不是MAP_PRIVATE,两者之间有一个显着的区别,后者执行写时复制,这可能会慢得多!

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

> ./fwrite 4096 1234
> Starting write...
> Starting mmap...
> write: 14767898 us, 14767.898000 us
> mmap: 6619623 us, 6619.623000 us
Run Code Online (Sandbox Code Playgroud)

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

  • 这不太可能比没有“mmap”的标准写入方法更快。 (2认同)