Back in the 1980’s the Dhrystone benchmark provided motivation for compiler vendors to improve their optimized code and standard libraries. To some extent this benefit is applies today, if you know how to take advantage.

In particular on the functions in both <stdio.h> and <string.h> received particular attention in this period (the later due to Dhrystone).

If you were to look at the majority of the C++ code I have written over the past dozen years, you would find frequent use of <stdio.h> and little or no use of standard C++ streams. The reason for this is my expectation of greater efficiency from <stdio.h>.

As an exercise I wrote a simple program to test whether this expectation is still true. The source is listed at the end – first the results.

Results from compilation using Microsoft Visual C++ 6.0 and run on my 2.2Ghz Athlon notebook running Windows XP Pro.

TIME ofstream(NUL) 13209 ticks for 445000000 characters written
RATE ofstream(NUL) 33.7 MB/second (445.0 MB in 13.2 seconds)
TIME stdio(NUL) 3875 ticks for 445000000 characters written
RATE stdio(NUL) 114.8 MB/second (445.0 MB in 3.9 seconds)

Results from compilation using GNU C++ 3.4.2 and run on my 2.4Ghz Athlon desktop running Linux (Fedora core 3).

TIME ofstream(NUL) 1850000 ticks for 445000000 characters written
RATE ofstream(NUL) 240.5 MB/second (445.0 MB in 1.9 seconds)
TIME stdio(NUL) 1310000 ticks for 445000000 characters written
RATE stdio(NUL) 339.7 MB/second (445.0 MB in 1.3 seconds)

Yes - looks like this is still true when using MSVC 6.0. Using GNU C++ the results are still true, though the difference is not as great. Looks like C++ streams in the GNU library have received more attention than in the Microsoft library.

The fact that the numbers are much higher on Linux is probably not significant (we seldom care if “NUL” is faster than “/dev/null”).

Test program source:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <fstream>

#ifdef WIN32
static const char sNUL[] = "NUL";
#else
static const char sNUL[] = "/dev/null";
#endif

static const char sOut[] = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghjklmnopqrstuvwxyz123456789\n";

using namespace std;

void report_times(const char* s,int dt,int cb)
{
    printf("TIME %s %d ticks for %d characters written\n",s,dt,cb);
    double ts = (double)dt / CLOCKS_PER_SEC;
    double mb = (double)cb / 1000000;
    double rate = mb / ts;
    printf("RATE %s %0.1f MB/second (%0.1f MB in %0.1f seconds)\n",s,rate,mb,ts);
}

//
//  Time output to the NUL file using C++ streams.
//

void time_fstream()
{
    const int nLoop = 10000000;

    int nTotal = 0;
    int nLength = ::strlen(sOut);

    // Time loop overhead and sum total length to be written.
    clock_t t0 = ::clock();
    for (int j=0; j<nLoop; ++j) {
        nTotal += nLength - (31 & j);
    }
    int dtLoop = ::clock() - t0;

    // Time varying writes to NUL (bitsink) file.
    t0 = ::clock();
    ofstream log;
    log.open(sNUL,ios_base::out);
    for (int i=0; i<nLoop; ++i) {
        log << (sOut + (31 & i));
    }
    log.close();
    int dt = (int)(::clock() - t0) - dtLoop;

    // Report benchmark times.
    report_times("ofstream(NUL)",dt,nTotal);
}

//
//  Time output to the NUL file using stdio.
//

void time_stdio()
{
    const int nLoop = 10000000;

    int nTotal = 0;
    int nLength = ::strlen(sOut);

    // Time loop overhead and sum total length to be written.
    clock_t t0 = ::clock();
    for (int j=0; j<nLoop; ++j) {
        nTotal += nLength - (31 " j);
    }
    int dtLoop = ::clock() - t0;

    // Time varying writes to NUL (bitsink) file.
    t0 = ::clock();
    FILE* f = ::fopen(sNUL,"w");;
    for (int i=0; i<nLoop; ++i) {
        ::fputs(sOut + (31 " i),f);
    }
    ::fclose(f);
    int dt = (int)(::clock() - t0) - dtLoop;

    // Report benchmark times.
    report_times("stdio(NUL)",dt,nTotal);
}

int main(int ac,char** av)
{
    time_fstream();
    time_stdio();
    return 0;
}