how much does exception handling cost, really?

34
How much does Exception Handling cost, really? Kevin Frei Visual C++ Code Generation & Tools http://blogs.msdn.com/freik

Upload: darcie

Post on 29-Jan-2016

19 views

Category:

Documents


0 download

DESCRIPTION

How much does Exception Handling cost, really?. Kevin Frei Visual C++ Code Generation & Tools http://blogs.msdn.com/freik. Pro’s of EH I’ve heard More centralized error handling & recovery More robust code More readable code. Cons of EH I’ve heard - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: How much does Exception Handling cost, really?

How much does Exception Handling cost, really?

Kevin Frei

Visual C++ Code Generation & Tools

http://blogs.msdn.com/freik

Page 2: How much does Exception Handling cost, really?

Reasons for this talk (too many assumptions) Pro’s of EH I’ve heard

More centralized error handling & recovery

More robust code More readable code

Cons of EH I’ve heard Can result in people not

thinking about error conditions

Can make error recovery difficult (must put handler in the “right” place)

Enables abuse of exceptions

Page 3: How much does Exception Handling cost, really?

Summary of the previous Pro’s & Con’s They can all be dealt with

Coding Convention enforcement Code Reviews Good initial architecture Consistent API designs

Page 4: How much does Exception Handling cost, really?

#1 reason I hear to not use EH: “Exception handling makes my code too slow”

May be true, but may also be masking a more serious problem

Some Facts: EH performance cost is dependent on the runtime, CPU

architecture, and ABI/OS specifics. You can’t simply examine source code to determine

performance impact. Deciding whether to use EH should depend on the team,

the libraries you’re using, and a myriad of other issues.

Page 5: How much does Exception Handling cost, really?

Classes of Code Quality impact Usage Penalty [EH tax]

General overhead of a function with any EH construct Cost of entering a protected region

__try{}, try{}, C++ object with a destructor Cleanup costs

__finally invocation C++ object destructors

Optimization constraints Cost of actually handling an exception

If you’re really concerned about this, you’re probably abusing exceptions.

Page 6: How much does Exception Handling cost, really?

EH tax for Structured Exception Handling X86

All functions with SEH contain a complex prolog & epilog

X64 No required cost to the function itself

Page 7: How much does Exception Handling cost, really?

EH tax for C++ exception handling X86

All functions with C++ EH contain a complex prolog & epilog

X64 1 additional DWORD allocated on stack, initialized

to -2 never again used in the function’s code It’s used by the C++ runtime in the event of an exception

being thrown or caught.

Page 8: How much does Exception Handling cost, really?

Protected Region entry & exit costs X86

Entry & exit from any protected region requires a 1 or 4 byte constant value written to the stack /EHs can reduce this cost /EHa may be required by your code base, though

X64 If an entry or an exit is preceded by a call, there is

a single byte NOP to properly identify region boundaries Entry preceded by a call is pretty common for C++ EH

(constructors)

Page 9: How much does Exception Handling cost, really?

Non-exception cleanup costs X86

SEH: __finally clause is called [current implementation, not required]

call/ret overhead Some other minor register allocation issues

C++EH: Destructor invoked inline [C++ standard] Destructor can be inlined, based on compiler (& user)

decision X64

SEH: __finally clause inlined [zero overhead] [again, current implementation, not required]

C++EH: same as x86

Page 10: How much does Exception Handling cost, really?

Optimization Constraints Disclaimer Consider the complete alternative solution!

HRESULT checking is messy, and error prone The goto solution to handle termination can

result in pessimized dataflow Most optimizations that must be constrained

for EH should be constrained for implementations that don’t use EH.

Page 11: How much does Exception Handling cost, really?

Optimization constraints

Mandatory optimization constraints Limitations required by the language standard ABI specific limitations

Current Implementation constraints I’ll focus on UTC (current optimizer) in VC8

Code base from VC5 origins. Many constraints have been removed, which exist in

earlier versions

Page 12: How much does Exception Handling cost, really?

Mandatory optimization constraints:Language specific limitations The C++ language standard does not specify

anything about non-C++ throw exceptions! The C language standard does not specify

anything about exceptions at all, really. [I know nothing about C99]

Page 13: How much does Exception Handling cost, really?

Language specific limitations: C++ Flow from try’s to catch (and out):

Results in additional flow edges at call sites that may throw exceptions Variable values must be updated accordingly Slightly less constant propagation, common sub

expression elimination, dead stores, etc… /EHs – assume only the C++ throw statement can

cause an exception Prior to VC8.0, you could compile /EHs, and even with an

AV, most destructors would be invoked. For VC8.0 /EHs:

If you throw a C++ exception, destructors will be run. If any other exception occurs, no destructors will run.

Page 14: How much does Exception Handling cost, really?

Language specific limitations: /EHa /EHa – all exceptions should be considered

when destroying C++ objects Results in far more potential flow from a try

block to a catch block Less stack packing (no stack pack prior to VC8) Much less constant propagation, common sub

expression elimination, etc…

Page 15: How much does Exception Handling cost, really?

Quick /EHc description

Only has impact with /EHs Tells the compiler that any extern “C”

function will not throw any C++ exceptions Win32 API calls fall under this class

Sometimes true, sometimes not – be careful. Only side effect is pruning a few additional

edges in the flow graph A few more opportunities for optimization

Page 16: How much does Exception Handling cost, really?

Mandatory Optimization Constraints:Win32/Win64 ABI specific limitations Tail-call (call/return -> jump) is illegal inside a

protected region Instruction level performance hit is typically

negligible Stack usage increase (can be serious)

Instruction scheduling constraints Scheduling into & out of handler regions is limited

rarely worth doing, even if it is legal

Page 17: How much does Exception Handling cost, really?

VC8.0 optimization constraints No impact on any functions that do not contain

some EH construct Sometimes requires the programmer add volatile to get

required constraints to occur in function invoked inside a try Exception handling is only one of a large number of

things that can artificially constrain optimizations setjmp/longjmp (old school EH in C) __alloca __declspec’s /GS /fp:except, /fp:precise, /fp:restrict Many many more.

Page 18: How much does Exception Handling cost, really?

VC8.0 optimization constraints:Specifics Late flow optimizations for x64

Primarily head & tail merging Loop optimizer disabled (all platforms) for any

function with a try/__try Loop unrolling/peeling Induction variable creation Some strength reduction Doesn’t impact functions with only C++ objects!

Stack Packing restrictions Prior to VC8, all variables inside a try block were written

back to the stack whenever their values were updated With VC8, only variable values that may be visible outside

of the try are written back to the stack.

Page 19: How much does Exception Handling cost, really?

Source code used for samples SEH Version

void seh_finally() { init(); __try { foo(); bar(); blah(); } __finally { done(); }}

C++ Version

struct obj { obj() {init();} ~obj() {done();}};

void cpp_dtor() { obj a; foo(); bar(); blah();}

No EH Versionint noeh_cleanup() {

int result = 0;

init();

result = foo_err();

if (result)

goto fail;

result = bar_err();

if (result)

goto fail;

result = blah_err();

fail:

done();

return result;

}

Page 20: How much does Exception Handling cost, really?

Generated code for x86 SEH /O2push ebpmov ebp, esppush -1push OFFSET __sehtable$?seh_finally@@YAXXZpush OFFSET __except_handler3mov eax, DWORD PTR fs:0push eaxmov DWORD PTR fs:0, espsub esp, 8 ;End Prologcall initmov DWORD PTR __$SEHRec$[ebp+20], 0 ;Enter __trycall foocall barcall blahmov DWORD PTR __$SEHRec$[ebp+20], -1 ;Exit __trycall $seh_finally_funclet ;Invoke __finallymov ecx, DWORD PTR __$SEHRec$[ebp+8] ;Begin Epiloguemov DWORD PTR fs:0, ecxmov esp, ebppop ebpret 0

$seh_finally_funclet:call doneret 0

Page 21: How much does Exception Handling cost, really?

Generated code for x86 SEH /O1push 8push OFFSET __sehtable$seh_finallycall __SEH_prolog ;End Prologuecall initand __$SEHRec$[ebp+20], 0 ;Entry __trycall foocall barcall blahor __$SEHRec$[ebp+20], -1 ;Exit __trycall $seh_finally_funclet ;Invoke __finallycall __SEH_epilog ;Begin Epilogueret 0

$seh_finally_funclet:call blahret 0

Page 22: How much does Exception Handling cost, really?

Generated code for x86 C++ /O2push -1push __ehhandler$?cpp_dtor@@YAXXZmov eax, DWORD PTR fs:0push eaxmov DWORD PTR fs:0, esp ;End Prologuepush ecx ;allocate space for objcall init ;obj() inlinedmov DWORD PTR __$EHRec$[esp+24], 0 ;Enter trycall foocall barcall blahmov DWORD PTR __$EHRec$[esp+24], -1 ;Exit trycall done ;~obj() inlinedmov ecx, DWORD PTR __$EHRec$[esp+16] ;Begin Epiloguemov DWORD PTR fs:0, ecxadd esp, 16ret 0

Page 23: How much does Exception Handling cost, really?

Generated code for x86 C++ /O1mov eax, __ehhandler$?cpp_dtor@@YAXXZcall __EH_prolog ;End Prologuepush ecx ;allocate space for

objcall init ;obj() inlinedand DWORD PTR __$EHRec$[ebp+8], 0 ;Entry trycall foocall barcall blahor DWORD PTR __$EHRec$[ebp+8], -1 ;Exit trycall done ;~obj() inlinedmov ecx, DWORD PTR __$EHRec$[ebp] ;Begin Epiloguemov DWORD PTR fs:0, ecxleaveret 0

Page 24: How much does Exception Handling cost, really?

Generated code for x86 No EH (/O1 & /O2 are basically identical)push esi ;Save nonvolatile register for result

call initcall foo_errmov esi, eax ;Save return codetest esi, esi ;Return code checkjne SHORT $failcall bar_errmov esi, eax ;Save return codetest esi, esi ;Return code checkjne SHORT $failcall blah_errmov esi, eax ;Save return code

$fail:call donemov eax, esi ;Return resultpop esiret 0

Page 25: How much does Exception Handling cost, really?

Generated code for x64 SEH

sub rsp, 40 ;End Prologuecall initnopcall foo ;First instruction of __trycall barcall blahnop ;Last instruction of __trycall done ;__finally invoked inlineadd rsp, 40 ;Begin Epilogueret 0

Page 26: How much does Exception Handling cost, really?

Generated code for x64 C++ EHsub rsp, 56 ;End Prologuemov QWORD PTR $T[rsp], -2 ; C++ setupcall initnopcall foo ;First instruction of trycall barcall blahnop ;Last instruction of tryadd rsp, 56 ;Begin Epiloguejmp done ;~obj() inlined & tail called

Page 27: How much does Exception Handling cost, really?

Generated code for x64 No EH

push rbx ;Save nonvolatile register for resultsub rsp, 32 ;End Prologuecall initcall foo_errmov ebx, eax ;Save return codetest eax, eax ;Return code checkjne SHORT $failcall bar_errmov ebx, eax ;Save return codetest eax, eax ;Return code checkjne SHORT $failcall blah_errmov ebx, eax ;Save return code

$fail:call donemov eax, ebx ;Get return codeadd rsp, 32pop rbx ;Restore nonvolatile registerret 0

Page 28: How much does Exception Handling cost, really?

Costs of handling an exceptionDisclaimer:

If you are really concerned about this, there is a good chance you’re abusing or misusing exceptions.

Exceptions are not to deal with standard scenarios! Performance of exceptions is generally stacked in favor of the non-exceptional case

There’s a reason the term is “exception”!

Page 29: How much does Exception Handling cost, really?

Costs of handling an exception:X86 – Win32 – SEH & C++ EH Without /SAFESEH (this is a big no-no – potential security hole) O(n)

n is the number of frames on the stack with a protected region between throw & catch Walk a linked list of elements on [fs:0] Invoke filters to determine handler

C++ type check is just a special filter Walk the list again, invoking __finally funclets & destructors Finally, jump to __except block or call catch block

With /SAFESEH (this is good) O(n log(m))

n is the number of frames on the stack with a protected region between throw & catch m is the number of EH entry points in the entire program

For SEH, only 1. For C++ EH, one for each function! Walk a linked list of elements of [fs:0] For each element, verify the callback is in a list [O log(m)] Invoke the filter to determine the handler Walk the list again, invoking __finally’s, with callback verification [O log(m)]

Page 30: How much does Exception Handling cost, really?

Costs of handling an exception:x64 – Win64 – SEH & C++ EH O(n log(m))

n is the number of functions on the stack between throw & catch (not just the number with EH code in them!)

m is the number of distinct regions in the image [.pdata size] Not just a function count – hot/cold sections and register allocation

regions can increase this pretty dramatically (1-4x) Walk each function frame on the stack [O(n)] Find it’s .pdata entry to get it’s unwind information [O(log(m))]

If it has a filter, call it to determine the handler Restore nonvolatile registers as described in the unwind information

Once a handler has been determined Walk the stack again (using .pdata lookup) Each frame that has cleanup code, invoke the finally’s or destructors

Jump to handler (or call catch)

Page 31: How much does Exception Handling cost, really?

Cost of handling an exception:x86 – WoW64 – SEH & C++EH There is some degree of thunking between

the 64 bit kernel and 32 bit subsystem, so performance really varies. Worst case, it’s as slow as x64 on Win64. Best case it’s about the same as x86 on Win32.

If you use exception handling in performance sensitive areas of code, you may notice a difference in your application If you do notice a difference, this should be a red

flag regarding your use of exceptions.

Page 32: How much does Exception Handling cost, really?

Final gotchas (non-standard C++!)

int g; // add a volatile to fix the problemint *p;

void func1() { g = 0; __try { g = 1; *p = 0; g = 2; } __except(1) { printf("%d\n", g); }}

void update() { g = 1; *p = 0; g = 2;}void func2() { g = 0; __try { update(); } __except(1) { printf("%d\n", g); }}

Some optimizations that are constrained inside of a try result in observable differences, based on program structure, compiler settings, and compiler implementation .

Page 33: How much does Exception Handling cost, really?

Summary & Conclusions

Do not use exceptions for normal program flow. Exception handling does have a performance cost

Not always measurable Cost really depends on usage Frequently similar to what correct code would be, without EH

[at least in VC8] Do not use exceptions for normal program flow. C++ is cheaper than SEH for cleanup in VC8. Use common sense, and knowledge of your team’s

strengths/weaknesses if you’re mandating SEH/C++ EH/No EH New hires rarely know about SEH. Source level readability & visibility of performance

And finally, do not use exceptions for normal program flow.

Page 34: How much does Exception Handling cost, really?

More info

If you’re looking for detailed ABI docs for X64, check my blog.

http://blogs.msdn.com/freik Herb Sutter’s got some good books on using

exceptions with C++ He doesn’t give me kick backs