Originally Posted by
MK27
Okay, I feel slightly guilty, here's what I was suggesting earlier, but it is maybe not much simpler than Sebastiani's idea. On the other hand, it involves more basic low level syntax and will be faster (and methinks, way more memory efficient), so despite my alleged "lack of familiarity of the language and the development processes used by C++ programmers", I'll stand by what I said before WRT appropriateness for the OP:
I guess my point is just that the performance difference isn't always going to be as much as you might think. You can write low-level C++ code, too, and moreover you can also get tons more "mileage" from it (as it can be designed to be reusable). Like I said, as a C++ programmer, you generally design from the top-down, to begin with, using high-level objects at first. Then, where you see performance problems you can simply start whittling down the code (preferably keeping the high-level interface, but working out the low-level details beneath that layer). And yes, I do believe that it can take some time to "grok" these methodologies - hell, it probably took me at least 5 years to become proficient at it.
I've put together an example to demonstrate my point a little clearer. Here is the "problem": we have to design a function to convert an array of characters to lowercase (signature: void (*)(char*)). So let's see how much faster it is in "straight C", compared with a fairly generic C++ implemention. Ready? Steady. Go!
Code:
/*
The problem:
Design a function that converts a null-terminated array of
bytes to lowercase, using the standard 'tolower' function
*/
#include <ctype.h> // for 'tolower'
/*
Our implementation in C - simple and fast
*/
void c_tolower_string( char* data )
{
for( char* seq = data; *seq; ++seq )
*seq = tolower( *seq );
}
/*
This class transforms a null-terminated sequence, and can be applied to
any data-type that conforms with the minimum interface - an 'iterator'
typedef, and a non-const 'begin' member function that returns an iterator
*/
template < typename Transform >
struct null_terminated_text_processor
{
inline null_terminated_text_processor( Transform transform = Transform( ) )
: transform( transform )
{ }
template < typename Container >
inline void operator ( ) ( Container& data )
{
for( typename Container::iterator seq = data.begin( ); *seq; ++seq )
transform( *seq );
}
Transform
transform;
};
/*
Helper generator
*/
template < typename Transform >
inline null_terminated_text_processor< Transform > make_null_terminated_text_processor( Transform transform )
{
return null_terminated_text_processor< Transform >( transform );
}
/*
Minimum interface required to interact with our generic code
*/
template < typename Iterator >
struct minimal_interface_container
{
typedef Iterator
iterator;
inline minimal_interface_container( Iterator seq )
: seq( seq )
{ }
inline Iterator begin( void )
{
return seq;
}
Iterator
seq;
};
/*
Our lowercase converter
*/
struct lowerizer
{
template < typename Char >
inline void operator ( ) ( Char& ch )
{
ch = tolower( ch );
}
};
/*
Just as an example
*/
struct vowel_lowerizer
{
template < typename Char >
inline void operator ( ) ( Char& ch )
{
Char
c = tolower( ch );
if( c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' )
ch = c;
}
};
/*
Our implementation in C++ - convoluted and slow?
*/
inline void cpp_tolower_string( char* data )
{
minimal_interface_container< char* >
stuff = data;
make_null_terminated_text_processor( lowerizer( ) )( stuff );
}
/*
Our test code
*/
#include <iostream>
#include <string>
using namespace std;
template < typename Container, typename Process >
void test( char* pid, Process process, size_t iterations, char* saved, char* data )
{
char
* svp,
* dtp;
clock_t
start,
stop;
cout << "Process: " << pid << endl;
start = clock( );
for( size_t i = 0; i < iterations; ++i )
{
/*
We could simply use memcpy here, but I
don't want it to mess with the results
*/
for( svp = saved, dtp = data; *svp; )
*dtp++ = *svp++;
Container
stuff = data;
process( stuff );
}
stop = clock( );
double
elapsed = double( stop - start ) / CLK_TCK;
cout << "Elapsed: " << elapsed << " seconds" << endl;
}
int main( void )
{
size_t const
size = 50000,
iterations = 1000;
char
saved[ size ],
data[ size ];
srand( time( 0 ) );
for( size_t i = 0; i < size; ++i )
saved[ i ] = rand( ) % 26 + ( rand( ) & 1 ) * ( 'a' - 'A' ) + 'A';
saved[ size - 1 ] = 0;
cout << " -- Test -- " << endl;
cout << "Total number of bytes to process: " << ( size * iterations ) << endl;
test< char* >( "c_tolower_string", c_tolower_string, iterations, saved, data );
test< char* >( "cpp_tolower_string", cpp_tolower_string, iterations, saved, data );
test< minimal_interface_container< char* > >
(
"lowerizer ( minimal_interface_container< char* > )",
make_null_terminated_text_processor( lowerizer( ) ),
iterations,
saved,
data
);
test< minimal_interface_container< char* > >
(
"vowel_lowerizer ( minimal_interface_container< char* > )",
make_null_terminated_text_processor( vowel_lowerizer( ) ),
iterations,
saved,
data
);
test< string >
(
"lowerizer ( minimal_interface_container< string > )",
make_null_terminated_text_processor( lowerizer( ) ),
iterations,
saved,
data
);
}
My output:
-- Test --
Total number of bytes to process: 50000000
Process: c_tolower_string
Elapsed: 1.981 seconds
Process: cpp_tolower_string
Elapsed: 2.149 seconds
Process: lowerizer ( minimal_interface_container< char* > )
Elapsed: 2.224 seconds
Process: vowel_lowerizer ( minimal_interface_container< char* > )
Elapsed: 2.574 seconds
Process: lowerizer ( minimal_interface_container< string > )
Elapsed: 3.386 seconds
So you see? Even though the C++ code is considerably more complex, it processes 50 million bytes of data just a fraction of a second slower. Moreover, the C++ code ultimately made our life easier, because now we can snap it together with a variety of data-structures and functionality. Delegation at it's best, IMO.