【问题标题】:Fastest way to copy data from one file to another in C/C++?在 C/C++ 中将数据从一个文件复制到另一个文件的最快方法?
【发布时间】:2019-04-26 20:17:42
【问题描述】:

在我的代码中,我需要将数据从一个文件复制到另一个文件。我想出的解决方案是这样的:

const int BUF_SIZE = 1024;
char buf[BUF_SIZE];

int left_to_copy = toCopy;
while(left_to_copy > BUF_SIZE)
{
    fread(buf, BUF_SIZE, 1, fin);
    fwrite(buf, BUF_SIZE, 1, fout);
    left_to_copy -= BUF_SIZE;
}

fread(buf, left_to_copy, 1, fin);
fwrite(buf, left_to_copy, 1, fout);

我的主要想法是可能有类似 memcpy 的东西,但用于文件中的数据。我只是给它两个文件流和字节总数。我搜索了一下,但我找不到任何这样的东西。

但如果这样的东西不可用,我应该使用多大的缓冲区来使传输最快?更大意味着更少的系统调用,但我认为它可能会弄乱系统上的其他缓冲或缓存。我应该动态分配缓冲区以便它只进行一对读/写调用吗?在这种特殊情况下,典型的传输大小从几 KB 到十几 MB 不等。

编辑:对于特定于操作系统的信息,我们使用的是 Linux。

EDIT2:

我尝试使用 sendfile,但它不起作用。它似乎写入了适量的数据,但它是垃圾。

我将上面的示例替换为如下所示:

fflush(fin);
fflush(fout);
off_t offset = ftello64(fin);
sendfile(fileno(fout), fileno(fin), &offset, toCopy);
fseeko64(fin, offset, SEEK_SET);

我添加了flush、offest 和一次寻找一个,因为它似乎不起作用。

【问题讨论】:

  • 似乎最快的方法是使用依赖于操作系统的 API。
  • 简单的ifstream i; ofstream o; /*open both*/; o << i.rdbuf(); 有什么问题?保证可移植性不容忽视...
  • fread() 和 fwrite() 有返回值。你应该检查/使用它们。
  • @ildjarn。抱歉,如果从示例中不清楚。在程序中,fin 和 fout 都已经定位为复制 toCopy 字节的特定数据块。
  • 这与我的观点无关。您的意思是说您想要部分文件副本而不是完整文件副本?

标签: c++ c file-io fwrite fread


【解决方案1】:

您需要告诉我们您的(所需)操作系统。适当的调用(或者说最合适的调用)将是非常系统特定的。

在 Linux/*BSD/Mac 中,您将使用 sendfile(2),它处理内核空间中的复制。

概要

 #include <sys/sendfile.h>
 ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

描述

sendfile() copies data between one file descriptor and another.  Because this
copying is done within the kernel, sendfile() is more efficient than the
combination of read(2) and write(2), which would require transferring data to
and from user space.

in_fd should be a file descriptor opened for reading and out_fd should be a
descriptor opened for writing.

进一步阅读:

sendfile 示例的服务器部分:

/*

Server portion of sendfile example.

usage: server [port]

Copyright (C) 2003 Jeff Tranter.


This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/


#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>


int main(int argc, char **argv)
{
  int port = 1234;           /* port number to use */
  int sock;                  /* socket desciptor */
  int desc;                  /* file descriptor for socket */
  int fd;                    /* file descriptor for file to send */
  struct sockaddr_in addr;   /* socket parameters for bind */
  struct sockaddr_in addr1;  /* socket parameters for accept */
  int    addrlen;            /* argument to accept */
  struct stat stat_buf;      /* argument to fstat */
  off_t offset = 0;          /* file offset */
  char filename[PATH_MAX];   /* filename to send */
  int rc;                    /* holds return code of system calls */

  /* check command line arguments, handling an optional port number */
  if (argc == 2) {
    port = atoi(argv[1]);
    if (port <= 0) {
      fprintf(stderr, "invalid port: %s\n", argv[1]);
      exit(1);
    }
  } else if (argc != 1) {
    fprintf(stderr, "usage: %s [port]\n", argv[0]);
    exit(1);
  }

  /* create Internet domain socket */
  sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock == -1) {
    fprintf(stderr, "unable to create socket: %s\n", strerror(errno));
    exit(1);
  }

  /* fill in socket structure */
  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons(port);

  /* bind socket to the port */
  rc =  bind(sock, (struct sockaddr *)&addr, sizeof(addr));
  if (rc == -1) {
    fprintf(stderr, "unable to bind to socket: %s\n", strerror(errno));
    exit(1);
  }

  /* listen for clients on the socket */
  rc = listen(sock, 1);
  if (rc == -1) {
    fprintf(stderr, "listen failed: %s\n", strerror(errno));
    exit(1);
  }

  while (1) {

    /* wait for a client to connect */
    desc = accept(sock, (struct sockaddr *)  &addr1, &addrlen);
    if (desc == -1) {
      fprintf(stderr, "accept failed: %s\n", strerror(errno));
      exit(1);
    }

    /* get the file name from the client */
    rc = recv(desc, filename, sizeof(filename), 0);
    if (rc == -1) {
      fprintf(stderr, "recv failed: %s\n", strerror(errno));
      exit(1);
    }

    /* null terminate and strip any \r and \n from filename */
        filename[rc] = '\0';
    if (filename[strlen(filename)-1] == '\n')
      filename[strlen(filename)-1] = '\0';
    if (filename[strlen(filename)-1] == '\r')
      filename[strlen(filename)-1] = '\0';

    /* exit server if filename is "quit" */
    if (strcmp(filename, "quit") == 0) {
      fprintf(stderr, "quit command received, shutting down server\n");
      break;
    }

    fprintf(stderr, "received request to send file %s\n", filename);

    /* open the file to be sent */
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
      fprintf(stderr, "unable to open '%s': %s\n", filename, strerror(errno));
      exit(1);
    }

    /* get the size of the file to be sent */
    fstat(fd, &stat_buf);

    /* copy file using sendfile */
    offset = 0;
    rc = sendfile (desc, fd, &offset, stat_buf.st_size);
    if (rc == -1) {
      fprintf(stderr, "error from sendfile: %s\n", strerror(errno));
      exit(1);
    }
    if (rc != stat_buf.st_size) {
      fprintf(stderr, "incomplete transfer from sendfile: %d of %d bytes\n",
              rc,
              (int)stat_buf.st_size);
      exit(1);
    }

    /* close descriptor for file that was sent */
    close(fd);

    /* close socket descriptor */
    close(desc);
  }

  /* close socket */
  close(sock);
  return 0;
}

【讨论】:

  • 对于 Linux,这听起来正是我正在寻找的。下周我可以试试看效果如何。
  • 您尝试过 sendfile 吗?实际上,读/写循环不可能像 sendfile 一样快。谷歌“sendfile”,看看有多少广泛使用的系统正在用 sendfile 替换他们的读/写循环……
  • @kay,我很欣赏您在第三个链接中显示的示例,但它返回“找不到页面”。您能否提供一个包含相同示例的新链接?谢谢!
  • @silvioprog,谢谢你通知我!我从web.archive.org 逐字复制了代码
【解决方案2】:

您可以做的一件事是增加缓冲区的大小。如果您有大文件,这可能会有所帮助。

另一件事是直接调用操作系统,无论您的情况如何。 fread()fwrite(). 有一些开销

如果您可以使用无缓冲例程并提供自己的更大缓冲区,您可能会看到一些显着的性能改进。

我建议从fread() 的返回值中获取写入的字节数,以跟踪您何时完成。

【讨论】:

  • 我也会尝试更大的缓冲区。是否有推荐的大小来像这样铲除数据?另外,我看不出如何使用“从返回值写入的字节数”来提供帮助。我的案例使用循环,而剩余量大于缓冲区的大小。如果我给它一个更大的数字,那只会溢出缓冲区。
  • 缓冲区的最佳大小取决于可用内存量和被复制文件的大小。你需要玩它。我过去分配了多达半兆(当然是动态分配的)。您可以简单地从要复制的字节数中减去 fread() 返回的值。更简单的是,您可以循环直到fread() 返回小于BUF_SIZE。在这种情况下,您甚至不需要确定文件的大小。没什么区别,但对我来说看起来很奇怪。
  • 好的,所以我做了分析,发现越大越好:我使用 time 命令发现 1K 缓冲区为 1m31,1M 缓冲区为 59s,12M 缓冲区为 26s (动态分配)。看起来使缓冲区与您计划复制的最大内容一样大是最快的,至少如果相对于 RAM 量的缓冲区大小使得交换不会成为问题。
【解决方案3】:

为您的目标操作系统考虑内存映射文件 I/O 可能是值得的。对于您正在谈论的文件大小,这是一种可行的方法,并且操作系统将比您做得更好。但是,如果您想编写可移植操作系统代码,这可能不是最好的方法。

这需要一些设置,但是一旦你设置好了,你就可以忘记循环代码,它基本上看起来就像一个 memcpy。

【讨论】:

    【解决方案4】:

    就快速阅读而言,您还可以选择文件映射 - 内存映射 I/O 使用 mmap(参见 mmap 手册页)。与传统 I/O 相比,它被认为更高效,尤其是在处理大文件时。

    mmap 实际上并不读取文件。它只是将其映射到地址空间。这就是它如此之快的原因,在您实际访问该地址空间区域之前没有磁盘 I/O。

    或者你可以先看到块大小,然后你可以继续阅读,这也被认为是高效的,因为编译器在这种情况下增强了优化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多