unexpected abstraction penalty in C++ | | |
class c {
int x;
public:
inline c() : x(0) {}
inline c(int i) { x = i; }
inline operator const int& () const { return x; }
inline operator int& () { return x; }
};
If I use objects of type `c' as if they were int's, I see about 1.5x
slow-down compared to using regular int's (timed using GCC-3.3.4 -O3). Is
it reasonable to expect modern compilers to optimize this, so that there is
no abstraction penalty? | | | | re: unexpected abstraction penalty in C++
* alex goldman:[color=blue]
> class c {
> int x;
> public:
> inline c() : x(0) {}
> inline c(int i) { x = i; }
> inline operator const int& () const { return x; }
> inline operator int& () { return x; }
> };[/color]
'inline' is superflous here, because the functions are defined in-class
which makes the automatically 'inline'.
Second, 'inline' is at best only a hint.
To direct your compiler to optimize for speed, use your compiler's options
and/or non-standard language extensions.
[color=blue]
> If I use objects of type `c' as if they were int's, I see about 1.5x
> slow-down compared to using regular int's (timed using GCC-3.3.4 -O3). Is
> it reasonable to expect modern compilers to optimize this, so that there is
> no abstraction penalty?[/color]
Don't know.
With the following main program:
int main( int nArgs, char* arg[] )
{
int forceCode = atoi( arg[1] );
int x = 0x1234 + forceCode;
x += 0x1111;
std::cout << x << std::endl;
c y = 0x1234 + forceCode;
y += 0x1111;
std::cout << y << std::endl;
}
Visual C++ 7.1, optimize for speed or full optimization, emits a
single instruction for the initialization of and addition to 'x',
; int x = 0x1234 + forceCode;
; x += 0x1111;
lea edx,[esi+2345h]
in contrast to two instruction for the code using your class, 'y',
; c y = 0x1234 + forceCode;
lea eax,[esi+1234h]
; y += 0x1111;
add eax,1111h
I don't understand why the compiler can see the optimization for 'x'
but not for 'y', having gone so far as to represent them identically.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail? | | | | re: unexpected abstraction penalty in C++
Alf P. Steinbach wrote:[color=blue]
> * alex goldman:
>[color=green]
>>class c {
>> int x;
>>public:
>> inline c() : x(0) {}
>> inline c(int i) { x = i; }
>> inline operator const int& () const { return x; }
>> inline operator int& () { return x; }
>>};[/color]
>
>
> 'inline' is superflous here, because the functions are defined in-class
> which makes the automatically 'inline'.
>
> Second, 'inline' is at best only a hint.
>
> To direct your compiler to optimize for speed, use your compiler's options
> and/or non-standard language extensions.
>
>
>[color=green]
>>If I use objects of type `c' as if they were int's, I see about 1.5x
>>slow-down compared to using regular int's (timed using GCC-3.3.4 -O3). Is
>>it reasonable to expect modern compilers to optimize this, so that there is
>>no abstraction penalty?[/color]
>
>
> Don't know.
>
> With the following main program:
>
> int main( int nArgs, char* arg[] )
> {
> int forceCode = atoi( arg[1] );
>
> int x = 0x1234 + forceCode;
> x += 0x1111;
> std::cout << x << std::endl;
>
> c y = 0x1234 + forceCode;
> y += 0x1111;
> std::cout << y << std::endl;
> }
>
> Visual C++ 7.1, optimize for speed or full optimization, emits a
> single instruction for the initialization of and addition to 'x',
>
> ; int x = 0x1234 + forceCode;
> ; x += 0x1111;
> lea edx,[esi+2345h]
>
> in contrast to two instruction for the code using your class, 'y',
>
> ; c y = 0x1234 + forceCode;
> lea eax,[esi+1234h]
> ; y += 0x1111;
> add eax,1111h
>
> I don't understand why the compiler can see the optimization for 'x'
> but not for 'y', having gone so far as to represent them identically.
>[/color]
IMHO it's related to the point of time when the compiler's constant
folding kick in. I'd expect it to operate primarily on POD leaving out
objects (even though they might act the way the OP implements them). I
suppose this could impose overhead in the dataflow analysis which
probably won't pay off in most cases.
Cheers
Chris | | | | re: unexpected abstraction penalty in C++
alex goldman wrote:[color=blue]
> class c {
> int x;
> public:
> inline c() : x(0) {}
> inline c(int i) { x = i; }
> inline operator const int& () const { return x; }
> inline operator int& () { return x; }
> };
>
>
> If I use objects of type `c' as if they were int's, I see about 1.5x
> slow-down compared to using regular int's (timed using GCC-3.3.4 -O3). Is
> it reasonable to expect modern compilers to optimize this, so that there is
> no abstraction penalty?[/color]
Well you don't show us your code that actually uses the class, but there
are a few differences between
int [100];
and
c [100];
The first is that with an array of ints (or any POD) the default
initialization is either static or omitted. In your case,
a dynamic initialization always occurs. | | | | re: unexpected abstraction penalty in C++
On 2005-05-29, alex goldman <hello@spamm.er> wrote:[color=blue]
> class c {
> int x;
> public:
> inline c() : x(0) {}
> inline c(int i) { x = i; }
> inline operator const int& () const { return x; }
> inline operator int& () { return x; }
> };
>
>
> If I use objects of type `c' as if they were int's, I see about 1.5x
> slow-down compared to using regular int's (timed using GCC-3.3.4 -O3). Is
> it reasonable to expect modern compilers to optimize this, so that there is
> no abstraction penalty?[/color]
Does it make any difference if you change that to operator int() ?
Cheers,
--
Donovan Rebbechi http://pegasus.rutgers.edu/~elflord/ | | | | re: unexpected abstraction penalty in C++
Ron Natalie wrote:
[color=blue]
> In your case, a dynamic initialization always occurs.[/color]
That's a very good point. I removed the initialization just to be sure, but
it didn't make a difference for speed. It's still 2:3 for int vs the class.
Here's one of the benchmarks I ran:
#include <iostream>
class c {
int x;
public:
c() {}
c(int i) { x = i; }
operator const int& () const { return x; }
operator int& () { return x; }
};
#define LOOP1(i, n) for((i) = -(n); (i) <= (n); ++(i))
#define LOOP(i, j, k, n, a) \
a = 0; \
LOOP1(i, n) LOOP1(j, n) LOOP1(k, n) \
a += k + j; return a
int f_i(int n) {
int i, j, k, acc;
LOOP(i, j, k, n, acc);
}
int f_c(c n) {
c i, j, k, acc;
LOOP(i, j, k, n, acc);
}
int main() {
// std::cout << f_i(1000) << '\n';
std::cout << f_c(1000) << '\n';
} | | | | re: unexpected abstraction penalty in C++
Donovan Rebbechi wrote:
[color=blue]
>
> Does it make any difference if you change that to operator int() ?
>[/color]
I can't change the non-const operator to int (), but changing the const one
makes no difference. | | | | re: unexpected abstraction penalty in C++
alex goldman wrote:[color=blue]
> Ron Natalie wrote:
>
>[color=green]
>> In your case, a dynamic initialization always occurs.[/color]
>
>
> That's a very good point. I removed the initialization just to be sure, but
> it didn't make a difference for speed. It's still 2:3 for int vs the class.
>[/color]
You might try adding operator += and ++ to see what difference that
makes, or alternatively a operator=(int). | | | | re: unexpected abstraction penalty in C++
I wrote a more comprehensive test of various abstraction penalties in C++.
Here's what I get on P4 with GCC-3.3.4 -O3 (agressive optimization &
inlining):
$ time ./a.out
f_int(1000) : 9.240s
f_class1(1000) : 13.890s
f_class2(1000) : 19.510s
f_method(1000) : 13.850s
f_macro(1000) : 9.320s
f<int>(1000) : 9.240s
f<c>(1000) : 19.490s
f_get1(1000) : 13.850s
f_get2(1000) : 32.660s
real 2m21.092s
user 2m20.928s
sys 0m0.133s
Lessons learned:
* regular accessors (getters & setters) didn't help
* very minor things can confuse the optimizer (class1 vs class2)
I'm be curious to know how other CPUs/compilers do. Program text follows.
#include <iostream>
#include <ctime>
#include <iomanip>
using namespace std;
double time() { return double(clock()) / CLOCKS_PER_SEC; }
#define TIME_INC(e, res) { \
double t1 = time(); \
(res) += (e); \
double t2 = time(); \
cout << setw(15) << #e << " : " \
<< setw(7) << fixed << setprecision(3) \
<< t2 - t1 << "s" << endl; \
}
class c {
public:
int x;
c() {}
c(int i) : x(i) {}
operator const int& () const { return x; }
operator int& () { return x; }
const int& i() const { return x; }
int& i() { return x; }
int get() const { return x; }
void set(int i) { x = i; }
};
#define LOOP1(i, n) for((i) = -(n); (i) <= (n); ++(i))
#define LOOP(i, j, k, n, a) \
a = 0; \
LOOP1(i, n) LOOP1(j, n) LOOP1(k, n) \
a += k + j; return a
int f_int(int n) {
int i, j, k, acc;
LOOP(i, j, k, n, acc);
}
int f_class1(c n) {
c i, j, k, acc;
LOOP(i, j, k, n, acc);
}
// the return type is different from the above
c f_class2(c n) {
c i, j, k, acc;
LOOP(i, j, k, n, acc);
}
int f_method(c n) {
c i, j, k, acc;
LOOP(i.i(), j.i(), k.i(), n.i(), acc.i());
}
// very similar, but 1.5x faster!
#define I(e) (e).x
int f_macro(c n) {
c i, j, k, acc;
LOOP(I(i), I(j), I(k), I(n), I(acc));
}
template<class T>
T f(T n) {
T i, j, k, acc;
LOOP(i, j, k, n, acc);
}
int f_get1(c n) {
c i, j, k, acc = 0;
for(i.set(-n.get()); i.get() <= n.get(); i.set(i.get() + 1))
for(j.set(-n.get()); j.get() <= n.get(); j.set(j.get() + 1))
for(k.set(-n.get()); k.get() <= n.get(); k.set(k.get() + 1))
acc.set(acc.get() + k.get() + j.get());
return acc;
}
// the return type makes a big difference:
c f_get2(c n) {
c i, j, k, acc = 0;
for(i.set(-n.get()); i.get() <= n.get(); i.set(i.get() + 1))
for(j.set(-n.get()); j.get() <= n.get(); j.set(j.get() + 1))
for(k.set(-n.get()); k.get() <= n.get(); k.set(k.get() + 1))
acc.set(acc.get() + k.get() + j.get());
return acc;
}
#define TIME(e) TIME_INC(e(1000), dummy)
int main() {
int dummy = 0;
TIME(f_int);
TIME(f_class1)
TIME(f_class2)
TIME(f_method);
TIME(f_macro);
TIME(f<int>);
TIME(f<c>);
TIME(f_get1);
TIME(f_get2);
return dummy;
} |  | | | | /bytes/about
We are a network of experts and professionals in IT and software development that help one another with answers to tough questions and share insights.
Get the best answers to your questions from over 226,510 network members.
|