Re: Pointers to member functions are NOT useless
Jonathan Lee <jonathan.lee.975@gmail.com>, on 24/07/2010 21:10:25, wrote:
On Jul 24, 7:57 pm, "Francesco S. Carta"<entul...@gmail.com> wrote:
You wasted cycles, and potentially, you heavily slowed down the program:
imagine if there are hundreds or thousands of such objects in a
simulation, would you then try to save as much as possible? I know I would.
This is conjecture, of course. Nor is your assessment about the
performance obvious. In my code, the switch could easily be replaced
with a jump table, which might not be any slower. Also, the use of
a pointer to member function removes the possibility of profile
guided optimization and will probably ruin cache prediction.
Indeed, my ignorance led me to assert something that cannot reasonably
be true in all the cases - furthermore if the code is open to
optimizations, in which case my assertion will be likely false, as I
understood from your words and as I have seen with my eyes: I've created
a test program to compare the performance of the two approaches, and the
version using pointers to member functions happens to be somewhat faster
only if, in the version using the switch, I prevent the compiler from
inlining the called functions.
If I allow the compiler to inline them, then the switch version becomes
significantly faster.
Testing, of course, would be necessary. But this example has gone from
"generally useful" to "niche optimization" :/
My fault, of course, but fortunately not my intention.
I'm still interested in the general question and any information on how
to do something similar (i.e. context-depending member function choice)
using templates will be welcome - I'm thinking about it from yesterday
and still I cannot even imagine where to start.
Thank you for your patience and for your feedback :-)
//-------
#include <iostream>
#include <iomanip>
#include <ctime>
//#define PREVENT_FUNCTION_INLINING
using namespace std;
enum Context {
get_sum,
get_difference,
get_average
};
struct Base {
#ifdef PREVENT_FUNCTION_INLINING
double add(double a, double b) const;
double subtract(double a, double b) const;
double mean(double a, double b) const;
#else
double add(double a, double b) const {
return a + b;
}
double subtract(double a, double b) const {
return a - b;
}
double mean(double a, double b) const {
return (a + b) / 2;
}
#endif
virtual void set_context(Context) = 0;
virtual double execute(double, double) const = 0;
double test(int iterations, Context c) {
set_context(c);
double a = 42;
double b = 78;
double temp = 0;
for (int i = 0; i < iterations; ++i) {
temp = a;
a = execute(a, b);
b = execute(b, temp);
}
return a + b;
}
};
struct UseSwitch : Base {
Context context;
void set_context(Context c) {
context = c;
}
double execute(double a, double b) const {
switch (context) {
case get_sum:
return add(a, b);
case get_difference:
return subtract(a, b);
case get_average:
return mean(a,b);
default:
return -42;
}
}
};
struct UsePointer : Base {
UsePointer() : ptr(&Base::add) {}
void set_context(Context c) {
switch (c) {
case get_sum:
ptr = &Base::add;
break;
case get_difference:
ptr = &Base::subtract;
break;
case get_average:
ptr = &Base::mean;
}
}
double execute(double a, double b) const {
return (this->*ptr)(a, b);
}
double (Base::*ptr)(double, double) const;
};
#ifdef PREVENT_FUNCTION_INLINING
double Base::add(double a, double b) const {
return a + b;
}
double Base::subtract(double a, double b) const {
return a - b;
}
double Base::mean(double a, double b) const {
return (a + b) / 2;
}
#endif
clock_t test(Base* obj, int iterations) {
int a, b, c;
clock_t start, total;
start = clock();
a = obj->test(iterations, get_sum);
b = obj->test(iterations, get_difference);
c = obj->test(iterations, get_average);
total = clock() - start;
cout << a << " " << b << " " << c << endl;
cout << "clocks: " << total << endl;
return total;
}
int main() {
Base* use_switch = new UseSwitch;
Base* use_pointer = new UsePointer;
const int iterations = 10000000;
const int loops = 10;
clock_t switch_clocks = 0;
clock_t pointer_clocks = 0;
for (int i = 0; i < loops; ++i) {
switch_clocks += test(use_switch, iterations);
pointer_clocks += test(use_pointer, iterations);
}
double switch_per_loop = switch_clocks / loops;
double pointer_per_loop = pointer_clocks / loops;
double switch_per_iteration = switch_per_loop / iterations / 3;
double pointer_per_iteration = pointer_per_loop / iterations / 3;
cout << setprecision(6) << fixed;
cout << "switch, per loop: " << switch_per_loop << endl;
cout << "switch, per iteration: " << switch_per_iteration << endl;
cout << endl;
cout << "pointer, per loop: " << pointer_per_loop << endl;
cout << "pointer, per iteration: " << pointer_per_iteration << endl;
cout << endl;
cout << "switch - pointer, per iteration difference: ";
cout << (switch_per_iteration - pointer_per_iteration) << endl;
return 0;
}
//-------
/*
Output with function inlining:
switch, per loop: 940.000000
switch, per iteration: 0.000031
pointer, per loop: 1480.000000
pointer, per iteration: 0.000049
switch - pointer, per iteration difference: -0.000018
Output without function inlining:
switch, per loop: 1726.000000
switch, per iteration: 0.000058
pointer, per loop: 1463.000000
pointer, per iteration: 0.000049
switch - pointer, per iteration difference: 0.000009
*/
--
FSC - http://userscripts.org/scripts/show/59948
http://fscode.altervista.org - http://sardinias.com