3

I need a cross-platform, no external library, way of copying a file. In my first pass I came up with (error handling omitted):

char buffer[LEN];
ifstream src(srcFile, ios::in | ios::binary);
ofstream dest(destFile, ios::out | ios::binary);

while (!src.eof()) {
  src.read(buffer, LEN);
  dest.write(buffer, src.gcount());
}

This worked nicely and I knew exactly what it was doing.

Then I found a post on stackoverflow (sorry, can't find a link right now) that says I can replace all of the above code with:

dest << src.rdbuf();

Which is nice and compact, but hides a lot about what it's doing. It also turns out to be really slow because the implementation of ofstream::operator<<(streambuf) moves things 1 character at a time (using snetxc()/sputc()).

Is there a way for me to make this method faster? Is there a drawback to my original method?

Update: There's something inefficient about using operator<<(streambuf) on windows. The .read()/.write() loop looks to always perform better than operator<<.

Also, changing the size of the buffer in the code above does not affect the size of the reads and writes to the hard drive. To do that you need to set the buffers using stream.rdbuf()->pubsetbuf().

Erik
  • 1,674
  • 3
  • 15
  • 18
  • why don't you `define` the copy for each platform? or use boost? – Daniel A. White Aug 30 '11 at 22:57
  • See http://stackoverflow.com/questions/829468/how-to-perform-boostfilesystem-copy-file-with-overwrite – Daniel A. White Aug 30 '11 at 22:58
  • 3
    How about `dest.write(src.rdbuf(), size);`? – Kerrek SB Aug 30 '11 at 23:07
  • @Kerrek : `write` takes a `char const*`, but `rdbuf` returns a `std::filebuf` -- how would that work? – ildjarn Aug 30 '11 at 23:15
  • 1
    "I need a cross-platform, no external library, way of" I'm not sure what circumstances would require one to need to be both cross platform _and_ be forbidden from using libraries. Especially since libraries are the most effective tools for cross-platform development. – Nicol Bolas Aug 30 '11 at 23:18
  • What are the average size of these files? What is the size of you computers memory? What other things are running? If small files, large memory, nothing else running usually - Store the lot in a buffer. Otherwise you need to think of a compromise. – Ed Heal Aug 30 '11 at 23:23
  • 1
    if you use plain c calls you'll see better performance. streams are crippled performance-wise. Don't use them if performance matters – Pavel P Aug 30 '11 at 23:25
  • I don't think the standard library can beat the first method you programmed, although for large files the second is much faster, because it buffers smaller chunks. You'll have to use an external library. I would recommend boost, which contains functions for this, is portable, and the C++0x libraries were partially modeled after them. – Mooing Duck Aug 30 '11 at 23:33
  • 2
    @Pavel: The streams were designed to work on large files easily. I believe they're only slightly slower than C for small files. – Mooing Duck Aug 30 '11 at 23:35
  • performance isn't critical and memory isn't limited. But when something so simple as adding a 1K buffer adds a 20x+ performance improvement, I wonder if I'm not using this thing properly. streambuf has the ability to operate on blocks, so I'm wondering why it can't be configured to do that. – Erik Aug 30 '11 at 23:38
  • @Erik: Because the squeaky wheel gets the grease. Many programmers don't use iostreams at all. In turn, library developers don't bother to optimize possible use cases. Also, there are so many ways to use iostreams that they can't really account for all of them. This `std::filebuf` trick, for example, is probably not the first thing that an implementer would think of as being a performance issue. – Nicol Bolas Aug 30 '11 at 23:41
  • @ildjarn: Oh, indeed. OK, how about `src.rdbuf().pbase()`? – Kerrek SB Aug 30 '11 at 23:44
  • @Kerrek : `pptr` and `pbase` are protected, so not directly. Anyway, I was really just hoping you knew something I didn't about `std::filebuf` that would have allowed that to work. :-P – ildjarn Aug 30 '11 at 23:47
  • What system is this on? Using strace on Linux (GCC 4.5.1) shows that the rdbuf() method makes calls to read/write using an 8k buffer. I'm wondering if the fstream implementation on your system is unbuffered. Also, have you tried changing the read/write buffer sizes on your streams? – Dave S Aug 30 '11 at 23:50
  • Windows VS2005. I've tried srcStream->rdbuf()->pubsetbuf(buffer), but obviously that doesn't work because the problem is in ofstream::operator<< using sgetc/sputc. Is there another buffer size I should be adjusting? – Erik Aug 31 '11 at 00:17
  • Well, I would expect that the bottleneck to be reads/writes of single characters to the disk. Did you set both the read and write buffers? The GCC implementation seems to do the sgetc/sputc as well, but that just copies memory, the write's to disk should still be blocks at a time. – Dave S Aug 31 '11 at 00:37
  • good point, I'll test it again tomorrow. – Erik Aug 31 '11 at 00:49
  • However, it could be like you said, their implementation ignores the buffers. I'd be curious if later versions of VS have the same behavior. – Dave S Aug 31 '11 at 01:27
  • I've added some comments about my findings under Dave's answer below. – Erik Aug 31 '11 at 05:58

3 Answers3

12

I wonder if your fstream is unbuffered by default. GCC 4.5.2 by default uses an internal buffer, but I don't think that's required by the standard. Have you tried using pubsetbuf (see below) to set a buffer for your in/out streams.

A quick test on my system, if I set LEN to 0 (and therefore unbuffered), it took 10 seconds to copy a 1 MB file. With a 4k buffer, it completed in less than a second.

#include <iostream>
#include <fstream>

int main() {
  using namespace std;
  const char* srcFile = "test.in";
  const char* destFile = "test.out";

  ifstream src;
  ofstream dest;

  const int LEN=8192;
  char buffer_out[LEN];
  char buffer_in[LEN];
  if (LEN) {
    src.rdbuf()->pubsetbuf(buffer_in, LEN );
    dest.rdbuf()->pubsetbuf(buffer_out, LEN);
  } else {
    src.rdbuf()->pubsetbuf(NULL, 0 );
    dest.rdbuf()->pubsetbuf(NULL, 0);
  }
  src.open(srcFile, ios::in | ios::binary);
  dest.open(destFile, ios::out | ios::binary);
  dest << src.rdbuf();

}
Dave S
  • 20,507
  • 3
  • 48
  • 68
  • 1
    Good catch. On windows (vs2005) the behavior of ofstream::operator<< does not use the buffer. – Erik Aug 31 '11 at 00:33
  • I've got some interesting results that I can't possibly fully explain in a comment here. But the basics: operator<< and .read()/.write() both use the rdbuf() buffer (it defaults to 4k). Changing the size of this buffer while using operator<< does affect the speed, but it's still never as fast as .read()/.write(). There's something else inefficient about operator<< that's really slowing things down on windows. – Erik Aug 31 '11 at 06:02
  • @Dave S wow - very helpful answer. I just spent hours wondering why an ifstream being returned from a method rather that used directly from the calling method was around 10 times slower - turns out it was the buffer. – David Hall Sep 29 '11 at 17:03
1

Of course the src.rdbuf method is slower. It's doing reading and writing at the same time. Unless you're copying to a different harddisk or some form of network or attached storage, that's going to be slower than reading a block and then writing a block.

Just because code is compact does not make it faster.


Since you can't overload operator<< for the std::filebuf (since it's already overloaded), there isn't much you can do. It's better to just use the method that works reasonably well.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Huh? He's alternating reads with writes. If `LEN` is suitably large (maybe several hundred KB or so), then there won't be a lot of disk thrashing. – Adam Rosenfield Aug 30 '11 at 23:35
  • I'm not asking why one way is faster. streambuf has the ability to operate on blocks, why wouldn't operator<< take advantage of that? – Erik Aug 30 '11 at 23:35
  • @Adam: I was talking about the `rdbuf` version. – Nicol Bolas Aug 30 '11 at 23:37
1

Try using the C stdio API instead, it can often be faster in many implementations (see this thread for some numbers), though not always. For example:

// Error checking omitted for expository purposes
char buffer[LEN];
FILE *src = fopen(srcFile, "rb");
FILE *dest = fopen(destFile, "wb");

int n;
while ((n = fread(buffer, 1, LEN, src)) > 0)
{
    fwrite(buffer, 1, n, dest);
}

fclose(src);
fclose(dest);
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • yeah, it's nice to have a language that doesn't do black magic behind your back ;) – Erik Aug 31 '11 at 02:19
  • Some implementations are faster. Others are slower. Either way, your loop is wrong though. Try `while ((n=fread(...))>0)`. As is, if it encounters a problem reading before reaching the end of the file, it'll go into an infinite loop. – Jerry Coffin Aug 31 '11 at 14:48
  • @Jerry: I did say "error checking omitted for expository purposes", but in any case it should now work if `fread` encounters an error (which would be highly unusual -- the most likely cause would be a dropped network connection while reading from a file on network storage such as AFS or NFS). – Adam Rosenfield Aug 31 '11 at 22:38