Най-бързият начин за копиране на данни от един файл в друг в C/C++?

В моя код имам ситуация, в която трябва да копирам данни от един файл в друг. Решението, което измислих, изглежда така:

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:

Опитах да използвам sendfile, но не се получи. Изглежда, че пише правилното количество данни, но беше боклук.

Замених примера си по-горе с нещо, което изглежда така:

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

Добавих флъш, offest и searching един по един, тъй като изглеждаше, че не работи.


person stands2reason    schedule 10.05.2012    source източник
comment
Със сигурност изглежда, че най-бързият начин да направите това ще включва API, зависими от операционната система.   -  person Lalaland    schedule 11.05.2012
comment
Какво не е наред с простото ifstream i; ofstream o; /*open both*/; o << i.rdbuf();? Гарантираната преносимост не е нещо за пренебрегване...   -  person ildjarn    schedule 11.05.2012
comment
fread() и fwrite() имат върнати стойности. Трябва да ги проверите/използвате.   -  person Ras    schedule 11.05.2012
comment
@ildjarn. Съжалявам, ако не е станало ясно от примера. В програмата fin и fout вече са позиционирани да копират определена част от данни от toCopy байта.   -  person stands2reason    schedule 11.05.2012
comment
Това не е от значение за моята гледна точка. Искате да кажете, че искате частично копие на файл, а не пълно копие на файл?   -  person ildjarn    schedule 11.05.2012
comment
За да добавите към @Ras точка, какво ще стане, ако върнатата стойност на fread() е по-малка от BUF_SIZE? В този случай ще пишете боклуци в fout. Освен това вашият код ефективно закръгля размера toCopy до следващото кратно на BUF_SIZE. В зависимост от вашата употреба, това може или не може да бъде проблем за вас.   -  person Greg Hewgill    schedule 11.05.2012


Отговори (4)


Едно нещо, което можете да направите, е да увеличите размера на вашия буфер. Това може да помогне, ако имате големи файлове.

Друго нещо е да се обадите директно на ОС, каквото и да е във вашия случай. Има малко режийни разходи в fread() и fwrite().

Ако можете да използвате небуферирани процедури и да предоставите свой собствен по-голям буфер, може да видите някои забележими подобрения в производителността.

Бих препоръчал да вземете броя байтове, записани от върнатата стойност от fread(), за да проследите, когато сте готови.

person Jonathan Wood    schedule 10.05.2012
comment
Ще пробвам и с по-голям буфер. Има ли препоръчителен размер за изхвърляне на данни по този начин? Също така, не виждам как можете да използвате броя на байтовете, записани от върнатата стойност, за да помогнете. Моят случай използва цикъл, докато оставащото количество е по-голямо от размера на буфера. Ако му дам по-голямо число, това просто ще препълни буфера. - person stands2reason; 11.05.2012
comment
Най-добрият размер на буфера зависи от това колко памет е налична и размера на файловете, които се копират. Ще трябва да си поиграете с него. Разпределял съм до половин мегабайт в миналото (динамично разпределен, разбира се). Можете просто да извадите стойността, върната от fread() от броя байтове за копиране. Дори по-лесно, можете просто да циклите, докато fread() върне по-малко от BUF_SIZE. В този случай дори няма да е необходимо да определяте размера на файла. Няма голяма разлика, но просто ми се стори странно. - person Jonathan Wood; 11.05.2012
comment
Добре, така че направих профилирането и открих, че по-голямото е по-добро: използвах командата за време и намерих 1m31 за 1K буфер, 59s за 1M буфер и 26s за 12M буфер (динамично разпределен). Изглежда, че да направите буфера толкова голям, колкото най-голямото нещо, което планирате да копирате, е най-бързо, поне ако размерът на буфера спрямо количеството RAM е такъв, че размяната няма да е проблем. - person stands2reason; 14.05.2012

Трябва да ни кажете вашата (желана) операционна система. Подходящите повиквания (или по-скоро най-подходящите повиквания) ще бъдат много специфични за системата.

В 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;
}
person kay    schedule 10.05.2012
comment
За Linux това звучи точно като това, което търся. Мога да опитам да го използвам следващата седмица и да видя как работи. - person stands2reason; 11.05.2012
comment
Опитахте ли sendfile? На практика няма начин цикълът за четене/запис да бъде толкова бърз, колкото sendfile. Потърсете в Google sendfile и вижте колко широко използвани системи работят върху замяната на техните цикли за четене/запис с sendfile... - person kay; 15.05.2012
comment
@kay, ще оценя примера, който показахте в третата връзка, но той връща Страницата не е намерена. Бихте ли предоставили нова връзка, съдържаща същия пример? Благодаря ти! - person silvioprog; 26.04.2019
comment
@silvioprog, благодаря ти, че ме уведоми! Копирах кода дословно от web.archive.org - person kay; 26.04.2019

Може да си струва да обмислите входно-изходния файл с карта на паметта за вашата целева операционна система. За размерите на файловете, за които говорите, това е жизнеспособен начин и операционната система ще оптимизира по-добре, отколкото можете да направите. Ако искате да напишете код за преносима ОС, това може да не е най-добрият подход.

Това ще изисква известна настройка, но след като го настроите, можете да забравите за кода за цикъл и той основно ще изглежда като memcpy.

person George D Girton    schedule 10.05.2012

Що се отнася до бързото четене, имам предвид, че можете също да изберете картографиране на файлове - I/O картографиран в паметта с помощта на mmap (вижте страницата с ръководство за mmap). Смята се, че е по-ефективен в сравнение с конвенционалните I/O, особено когато се работи с големи файлове.

mmap всъщност не чете файла. Просто го картографира към адресно пространство. Ето защо е толкова бърз, няма диск I/O, докато действително не получите достъп до този регион на адресното пространство.

Или можете първо да видите размера на блока и според това можете да продължите да четете, което също се счита за ефективно, тъй като компилаторът подобрява оптимизацията в този случай.

person john    schedule 11.05.2012