Recursive search examples, pt3: C++
In two previous posts, I looked at implementations of a simple recursive-search task, implemented first in C# and then in C. In this post I'll examine how to write the same algorithm in C++, and continue the comparative performance analysis.
The first two implementations were in C# (using recls 100% .NET) and in C (using recls). Defining the C++ implementation has taken a while longer, as I wanted to wait until the release of recls 1.9, in order to utilise the substantial improvements in the C++ API over version 1.8. That's now available, and, so in this post I'll examine how to write the same algorithm in C++, and update the continue the comparative performance analysis started in the second part of this series.
Here's the code:
<strong>#include <recls/recls.hpp></strong>
#include <fastformat/fastformat.hpp>
#include <fastformat/sinks/ostream.hpp>
#include <stlsoft/string/case_functions.hpp>
int main()
{
<strong>recls::search_sequence</strong> files(NULL, "*.cs", <strong>recls::RECURSIVE</strong> | <strong>recls::RECLS_F_DIRECTORY_PARTS</strong>);
unsigned numFiles = 0;
stlsoft::uint64_t totalSize = 0;
{ for(recls::search_sequence::const_iterator i = files.begin(); i != files.end(); ++i)
{
if("ASSEMBLYINFO.CS" == stlsoft::to_upper((*i).get_file()))
{
<strong>recls::directory_parts</strong> const parts((*i).get_directory_parts());
if(!parts.empty())
{
if("PROPERTIES\\" == stlsoft::to_upper(*(parts.end() - 1)))
{
continue;
}
}
}
++numFiles;
totalSize += (*i).get_size();
}}
fastformat::fmtln(std::cout, "{0} file(s); {1} bytes", numFiles, totalSize);
return EXIT_SUCCESS;
}
This code illustrates the simple nature of the recls C++ API. Interestingly, it more closely approximates the C# version than the C version, showing that, in suitable cases (and with suitable libraries), C++ application code can be in the ballpack with C# for expressiveness.
For convenience, I've also used the FastFormat library for results output for portability, because 64-bit integers are not universally acceptable to the IOStreams. Consequently the program is portable across all platforms that are supported by recls (and FastFormat), which include Linux, Mac OS-X and Windows (including 32-/64-bit versions of each).
Now let's look at the performance. Further to the performance criteria examined in the second part of this series (time(s), handles, "I/O Other"), I've also updated each of the programs to yield its maximum working set size. The results for executing each of the programs under the same conditions on my host system (32-bit Windows) are shown in the following table. As before, Handles and "I/O Other" metrics were observed via Task Manager, and are therefore approximate.
| Language | Average Elapsed Time (ms) | Average Kernel Time (ms) | Average User Time (ms) | Maximum Working Set (KB) | Observed Handles | Observed "I/O Other"s |
|---|---|---|---|---|---|---|
| C | 9,429 | 4,656 | 4,500 | 1,380 | ~23 | ~230,000 |
| C++ | 9,753 | 4,812 | 4,531 | 1,380 | ~20 | ~230,000 |
| C#/.NET | 26,515 | 15,984 | 10,125 | 13,512 | ~80 | ~641,000 |
The results seem pretty clear cut. Accessing the file-system in .NET is substantially more expensive in time than the lower level accesses performed by C and C++. It also appears that it's more expensive in memory, but that's a long bow to draw from such a small test: it could equally be that the .NET runtime has a higher overhead, but further memory use increases linearly with algorithmic complexity in a roughly similar manner to C/C++. Further, more complex, tests may reveal more ...

