Imaginary losses
Posted Mar 27, 2008 20:14 UTC (Thu) by
wahern (subscriber, #37304)
In reply to:
Imaginary losses by ncm
Parent article:
Striking gold in binutils
Modern C++ exceptions might be conceptually zero-cost, but it is not less work than comparable C code. The difference is in how the stack is prepared to call the function. There is, evidently, a small fixed cost in every C++ function call which offsets the lack of a test+jump after the call. I admit I was unfamiliar w/ the details of modern exception handling, but I'm glad you forced my hand, because if anything we're cutting through some hyperbole.
Also, the error handling pattern in my C code doesn't duplicate as much code as the straw man examples posted here. I'm perfectly capable of using "goto" to jump to a common error handling block within a function, achieving something similar to the range table method of placing the error handling logic outside of the main execution flow. And I do this most of the time, because it just makes sense, and I get, IMO, better readability than exceptions, because there are fewer syntactic blocks to obscure my code. (I admit, that's highly subjective.)
Here's the example you requested. I used GCC--gcc version 4.0.1 (Apple Inc. build 5465)--with -O2 optimization. To compile: [cc|c++] -S -02 -o ex.S ex.c -DCPLUSPLUS=[0|1]
#if CPLUSPLUS
#include <iostream>
void noargs(int i) {
if (i > 1)
throw i;
return /* void */;
}
int main (int argc, char *argv[]) {
try {
noargs(argc);
} catch (int e) {
_Exit(e);
}
return 0;
}
#else
#include <stdio.h>
int noargs(int i) {
if (i > 1)
return i;
return 0;
}
int main(int argc, char *arg[]) {
int e;
if (0 != (e = noargs(argc))) {
_Exit(e);
}
return 0;
}
#endif
Simple, straight-forward code. Let us count the number of instructions from main() to our call to noargs(), and from return from the noargs() to leaving main().
C++ output:
.globl _main
_main:
LFB1481:
pushl %ebp
LCFI4:
movl %esp, %ebp
LCFI5:
pushl %esi
LCFI6:
subl $20, %esp
LCFI7:
movl 8(%ebp), %eax
movl %eax, (%esp)
LEHB0:
call __Z6noargsi
LEHE0:
addl $20, %esp
xorl %eax, %eax
popl %esi
leave
ret
On the "fast-path", we have 12 instructions for C++.
Now, plain C:
globl _main
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call _noargs
testl %eax, %eax
jne L10
leave
xorl %eax, %eax
ret
And in C, we have... 11 instructions. Well, well! And I'm being charitable, because in fact there are additional instructions for noargs() which increase the disparity: 8 in C, 12 in C++. That makes the total count 19 to 24, but for simplicity's sake, I'm happy to keep things confined to the caller.
Explain to me how this is a poor example. I'm willing to entertain you, and I by no means believe that this little example is conclusive. But, it seems pretty telling to me. I admit, I'm surprised how close they are. Indeed, if anybody suggested to me that C++ exceptions introduced too much of a runtime cost, I'd set them straight. But if they looked me straight in the eye and told me unequivocally that they were faster, I'd show them the door.
(
Log in to post comments)