Implementing normal algorithms using predicate versions
Chris Jefferson
caj@cs.york.ac.uk
Mon Nov 29 14:50:00 GMT 2004
Paolo Carlini wrote:
> Nathan Myers wrote:
>
>> Historical note... in the original STL, almost everything was marked
>> inline so that it could all be put in header files, without need for
>> all the bother and variation around .o files, object libraries, and
>> linking. Probably the inlines Gaby cites are leftovers from that.
>>
>> Stepanov thought that the number of different uses the tree-based
>> types in any given program would be small enough that inlining
>> wouldn't cost
>> much; also, that the compiler could make the choice to un-inline a
>> function marked inline but "too big", but would not choose the reverse.
>>
>>
> Thanks for the (interesting) note!
>
> Which is your opinion about my point (i.e, some times, for various
> reasons,
> -O2 is not usable and perhaps we don't want to penalize too much from the
> performance point of view unoptimized build)?!? I'm certainly up for
> Gaby/
> Benjamin/Chris proposal, but that issue worries me a little bit...
>
> Paolo.
BTW, sorry, I've recently been posting as Chris Jefferson, Chris and
caj. I didn't notice as I tend to disable names and just display e-mail
address on my mail client ¬_¬
I tried experimenting with some functions are various levels of
optimisation. Unsuprisingly at -O0 using a predicate causes quite a
speed hit as While at -O2 the output looks about identical, at -O1 while
it will inline the predicate, the optimiser doesn't seem able to
(doesn't want to?) totally "fold" the inlined function. For example the
function __unguarded_linear_insert in normal sort at -O1 is:
void std::__unguarded_linear_insert(_RandomAccessIterator, _Tp) [with
_RandomAccessIterator = int*, _Tp = int] (__last, __val)
{
int * __next;
int D.12360;
<bb 0>:
__next = __last;
__next = __next - 4B;
goto <bb 2> (<L1>);
<L0>:;
D.12360 = *__next;
*__last = D.12360;
__last = __next;
__next = __next - 4B;
<L1>:;
D.12360 = *__next;
if (D.12360 > __val) goto <L0>; else goto <L2>;
<L2>:;
*__last = __val;
return;
}
whereas at -O1 with a comparison object (which just calls operator <) is:
void std::__unguarded_linear_insert(_RandomAccessIterator, _Tp,
_Compare) [with _RandomAccessIterator = int*, _Tp = int, _Compare =
compare] (__last, __val, __comp)
{
const int D.12624;
const int D.12623;
int D.12622;
const int & a;
const int & b;
int * __next;
int __val.68;
int D.12453;
bool D.12451;
bool retval.67;
int D.12621;
<bb 0>:
__next = __last;
__next = __next - 4B;
goto <bb 2> (<L1>);
<L0>:;
D.12453 = *__next;
*__last = D.12453;
__last = __next;
__next = __next - 4B;
<L1>:;
a = &__val;
b = __next;
D.12623 = *a;
D.12624 = *b;
D.12622 = D.12623 < D.12624;
D.12621 = D.12622;
D.12451 = (bool) D.12621;
retval.67 = D.12451;
if (retval.67) goto <L0>; else goto <L3>;
<L3>:;
__val.68 = __val;
*__last = __val.68;
return;
}
However, it's is quite as bad as that when we turn into (for example)
x86 assembler. The first version using a comparison object becomes:
LFB684:
pushl %ebp
.LCFI0:
movl %esp, %ebp
.LCFI1:
pushl %ebx
.LCFI2:
movl 8(%ebp), %ecx
movl 12(%ebp), %ebx
leal -4(%ecx), %edx
cmpl -4(%ecx), %ebx
jge .L2
.L7:
movl (%edx), %eax
movl %eax, (%ecx)
leal -4(%edx), %eax
cmpl -4(%edx), %ebx
jge .L4
movl %edx, %ecx
movl %eax, %edx
jmp .L7
.L4:
movl %edx, %ecx
.L2:
movl %ebx, (%ecx)
popl %ebx
popl %ebp
ret
whereas the version without becomes:
.LFB684:
pushl %ebp
.LCFI0:
movl %esp, %ebp
.LCFI1:
pushl %esi
.LCFI2:
pushl %ebx
.LCFI3:
movl 8(%ebp), %ecx
movl 12(%ebp), %esi
leal -4(%ecx), %edx
movl -4(%ecx), %eax
cmpl %eax, %esi
jge .L2
.L7:
movl %eax, (%ecx)
leal -4(%edx), %ebx
movl -4(%edx), %eax
cmpl %esi, %eax
jle .L4
movl %edx, %ecx
movl %ebx, %edx
jmp .L7
.L4:
movl %edx, %ecx
.L2:
movl %esi, (%ecx)
popl %ebx
popl %esi
popl %ebp
ret
I'm not an expert on x86 assembler, so I'm not positive which of these
is better (the one with comparison operator seems to use one less
register but a few more instructions). However I'm willing to believe
that the difference between these is just "line noise" in the optimiser.
I've tried this on the entire of sort and the story appears to be that
-O1 is sufficent to remove any inefficency. -O0 gets hurt quite badly
however.
Chris
More information about the Libstdc++
mailing list