在 C/C++ 中将数据从一个文件复制到另一个文件的最快方法

Fastest way to copy data from one file to another in C/C++?

本文关键字:文件 复制 另一个 一个 方法 C++ 数据      更新时间:2023-10-16

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

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。

编辑2:

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

我用如下所示的内容替换了上面的示例:

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

我一次添加了冲洗、关闭和搜索一个,因为它似乎不起作用。

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

在 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.

延伸阅读:

  • Linux 发送文件源
  • 了解 sendfile(( 和 splice((
  • "发送文件示例的服务器部分"←示例 C 代码(存档链接,粘贴在下面(

发送文件示例的服务器部分:

/*
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: %sn", 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: %sn", 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: %sn", strerror(errno));
    exit(1);
  }
  /* listen for clients on the socket */
  rc = listen(sock, 1);
  if (rc == -1) {
    fprintf(stderr, "listen failed: %sn", 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: %sn", 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: %sn", strerror(errno));
      exit(1);
    }
    /* null terminate and strip any r and n from filename */
        filename[rc] = '';
    if (filename[strlen(filename)-1] == 'n')
      filename[strlen(filename)-1] = '';
    if (filename[strlen(filename)-1] == 'r')
      filename[strlen(filename)-1] = '';
    /* exit server if filename is "quit" */
    if (strcmp(filename, "quit") == 0) {
      fprintf(stderr, "quit command received, shutting down servern");
      break;
    }
    fprintf(stderr, "received request to send file %sn", filename);
    /* open the file to be sent */
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
      fprintf(stderr, "unable to open '%s': %sn", 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: %sn", strerror(errno));
      exit(1);
    }
    if (rc != stat_buf.st_size) {
      fprintf(stderr, "incomplete transfer from sendfile: %d of %d bytesn",
              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;
}

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

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

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

我建议从fread()返回值中获取写入的字节数,以便在完成后进行跟踪。

考虑

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

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

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

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

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