13

I've been working on a fairly large C++ project for a few weeks now. My original goal was to use this project to learn about C++11 and use only pure C++ code and avoid manual allocation and C constructs. However, I think this problem is going to force me to use C for a small function and I'd like to know why.

Basically I have a save function that will copy a somewhat large binary file to a separate location before I make changes to the data in it. The files themselves are CD images with a max size of around 700MB. Here is the original C++ code that I used:

std::ios::sync_with_stdio(false);

std::ifstream in(infile, std::ios::binary);
std::ofstream out(outfile, std::ios::binary);

std::copy(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>(), std::ostreambuf_iterator<char>(out));

out.close();
in.close();

This code when used with a 690MB file takes barely under 4 minutes to complete. I have ran it with multiple files and it's always the same result; nothing under 3 minutes. However, I also found the following way which ran a little bit faster, but still nowhere as fast as C:

std::ios::sync_with_stdio(false);

std::ifstream in(infile, std::ios::binary);
std::ofstream out(outfile, std::ios::binary);

out << in.rdbuf();

out.close();
in.close();

This one took 24 seconds, but it's still around 20 times slower than C.

After looking around I found someone needing to write an 80GB file and seeing that he could write at full speed using C. I decided to give it a try with this code:

FILE *in = fopen(infile, "rb");
FILE *out = fopen(outfile, "wb");

char buf[1024];
int read = 0;

//  Read data in 1kb chunks and write to output file
while ((read = fread(buf, 1, 1024, in)) == 1024)
{
  fwrite(buf, 1, 1024, out);
}

//  If there is any data left over write it out
fwrite(buf, 1, read, out);

fclose(out);
fclose(in);

The results were pretty shocking. Here is one of the benchmarks I have after running it multiple times on many different files:

File Size: 565,371,408 bytes
C  :   1.539s | 350.345 MB/s
C++:  24.754s | 21.7815 MB/s - out << in.rdbuf()
C++: 220.555s | 2.44465 MB/s - std::copy()

What is the cause of this vast difference? I know C++ won't match the performance of plain C, but 348MB/s difference is massive. Is there something I'm missing?

Edit:

I am compiling this using Visual Studio 2013 on a Windows 8.1 64-bit OS.

Edit 2:

After reading John Zwinck's answer I decided to just go the platform specific route. Since I still wanted to make my project cross-platform I threw together a quick example. I am really not sure if these work on the other systems besides Windows, but I can test Linux at a later date. I cannot test OSX, but I think copyfile looks like a simple function so I assume it's correct.

Keep in mind you need to do the same #ifdef logic for including platform specific headers.

void copy(std::string infile, std::string outfile)
{
#ifdef _WIN32 || _WIN64
  //  Windows
  CopyFileA(infile.c_str(), outfile.c_str(), false);
#elif __APPLE__
  //  OSX
  copyfile(infile.c_str(), outfile.c_str(), NULL, COPYFILE_DATA);
#elif __linux
  //  Linux
  struct stat stat_buf;
  int in_fd, out_fd;
  offset_t offset = 0;

  in_fd = open(infile.c_str(), O_RDONLY);
  fstat(in_fd, &stat_buf);
  out_fd = open(outfile.c_str(), O_WRONLY | O_CREAT, stat_buf.st_mode);

  sendfile(out_fd, in_fd, &offset, stat_buf.st_size);

  close(out_fd);
  close(in_fd);
#endif
}
ildjarn
  • 62,044
  • 9
  • 127
  • 211
ozdrgnaDiies
  • 1,909
  • 1
  • 19
  • 34
  • 4
    Which OS, which compilers, which C and C++ runtimes? This is basically unanswerable without more detail. (My magic 8-ball thinks the `std::copy` implementation will turn out to have been calling `read` and `write` one character at a time, but there's no intrinsic reason it *has* to do that; this is entirely a QoI question.) – zwol Apr 20 '14 at 03:48
  • @Zack I will add an edit with that information. – ozdrgnaDiies Apr 20 '14 at 03:49
  • 1
    Not sure what exactly is happening but I suppose your C++ versions are waiting on buffers to flush, while the C version just push it all and let the system do its thing. This C version will be particularly fast in Linux, because Linux allows write buffers to extend to all available memory, so you are not really writing to a file but just to the memory. The operational system is writing to the actual file in background. – Havenard Apr 20 '14 at 03:49
  • Possible duplicate of http://stackoverflow.com/q/22759885/2724703 – Mantosh Kumar Apr 20 '14 at 03:55
  • See also: http://stackoverflow.com/questions/10195343/copy-a-file-in-an-sane-safe-and-efficient-way – Cookyt Apr 20 '14 at 03:57
  • Note that basic IO syscalls like `open() read() write() close()` do not implement buffers so they can be very inefficient when dealing with streams that are not stored in RAM. The lib functions `fopen() fread() fwrite() fclose()` in the other hand can be much more efficient in this aspect for the reasons mentioned above. It can explain why it is slower in C++, maybe it is not using the full capabilities of buffering. Howover, I suspect the C++ is better one, because the C version is not giving you a real pespective of time taken to read and write a file in the same disk. – Havenard Apr 20 '14 at 04:00
  • You sure as hell won't be able to simultaneously read and write 350MB/s of data to the same HDD because of [Seek time](http://en.wikipedia.org/wiki/Seek_time#SEEKTIME). – Havenard Apr 20 '14 at 04:01
  • @Cookyt I looked at that before I created the question. Part of what I'm wondering is why his results show `std::copy` and `out << in.rdbuf()` to be the same time while mine are a factor of 10 apart. – ozdrgnaDiies Apr 20 '14 at 04:04
  • @Havenard I am running this on an SSD, so maybe that contributes to the high read/write speeds? I should have added that to the edit. – ozdrgnaDiies Apr 20 '14 at 04:05
  • Note that in the general case your C copy implemenation is wrong and may not copy the wole file: read() is not required to return the requested number of bytes; it can return less even before it hits eof. Ok, For disk files the only reason that POSIX allows for a low count is that the read was interrupted, so your prog will likely run fine -- until you get the idea to test pipe performance or such. Cf. http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html, or (better readable) http://linux.die.net/man/2/read. This kind of error is a good reason to use library functions. – Peter - Reinstate Monica Apr 20 '14 at 05:02
  • @PeterSchneider Thanks for the information. It was a quick and dirty implementation to test stuff, but I think it's good to be aware of pitfalls like that even if they don't directly affect the code at the moment. I wasn't aware that `read` could return smaller amounts at times other than EOF. Is it the same case for `fread`? – ozdrgnaDiies Apr 20 '14 at 05:08
  • 1
    And your c++ implementation is wrong as well. It works, but you should not copy a byte at a time. The way to do it in C++ is by using rdbuf(). `std::ifstream src("test", std::ios::binary); std::ofstream dst("to", std::ios::binary); dst << src.rdbuf();` – Ivan Apr 20 '14 at 08:25
  • @Ivan That's been in the question since it was posted. It's the second bit of C++ code. – ozdrgnaDiies Apr 20 '14 at 16:51
  • Please note that copyfile in linux may be used to copy files with max size of two GB, then it fails, also on 64bit systems and also with -D_FILE_OFFSET_BITS=64 – gabry Apr 03 '19 at 10:13

1 Answers1

9

First, you should also benchmark against copying the same file using the CLI on the same machine.

Second, if you want maximum performance you need to use a platform-specific API. On Windows that is probably CopyFile/CopyFileEx, on Mac OS it's copyfile, and on Linux it's sendfile. Some of those (definitely sendfile) offer performance which cannot be achieved using the basic portable stuff in C or C++. Some of them (CopyFileEx and copyfile) offer extra features such as copying filesystem attributes and optional progress callbacks.

You can see some benchmarks showing how much faster sendfile can be here: Copy a file in a sane, safe and efficient way

Finally, it is sad but true that C++ iostreams are not as fast as C file I/O on many platforms. If you care a lot about performance, you may be better off using C functions. I've encountered this when doing programming contests where runtime speed matters: using scanf and printf instead of cin and cout makes a big difference on many systems.

Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Will platform specific functions always perform noticeably better than the C code above? If it's at all possible I'd like to avoid having to keep up with 3 different implementations for file operations, but if it truly is better then I will go with it. As for CLI copying, it takes roughly 2-4 ms to copy which is much faster than anything posted above. – ozdrgnaDiies Apr 20 '14 at 04:16
  • 5
    Just if anybody is interested: copyfile() on OS X is open source (https://www.opensource.apple.com/source/copyfile/copyfile-42/copyfile.c), and it uses a plain read()/write() loop for copying the data part in copyfile_data(), with a buffer size equal to the file systems buffer size. - All the other stuff in copyfile is for copying metadata (extended attributes, ...). – Martin R Apr 20 '14 at 04:20
  • @ozdrgnaDiies what system setup are you running that you can copy a 500mb file in 2-4ms? Perhaps it isn't doing what you think it is. – 0x41414141 Apr 20 '14 at 09:27
  • @bh3244 - yeah, I'm sure it can queue up the copy request in 2-4ms. – Martin James Apr 20 '14 at 11:13