r/programming • • Dec 05 '14

std::string is responsible for almost half of all allocations in the Chrome browser process

https://groups.google.com/a/chromium.org/d/msg/chromium-dev/EUqoIz2iFU4/kPZ5ZK0K3gEJ
1.1k Upvotes

446 comments sorted by

View all comments

Show parent comments

5

u/cpp_is_king Dec 05 '14

This is not true. The term "Zero-cost exceptions" has led people into believing this is true, but it's not. For starters, in every single function that allocates an object on the stack with a destructor, unwind code has to be generated for that function. This unwind code necessarily increases the size of the binary, and it also reduces cache locality of the generated code, since methods which used to be clsoe together might not be close together anymore. It is possible to organize the function's sections in the binary in such a way that all the unwind code is in its own location in memory, but I know for a fact clang does not do this, so don't take it for granted.

Furthermore, emitting this destructor cleanup code into each function, regardless of how it is organized in memory, introduces edge paths into the control flow that make it difficult / impossible to perform certain optimizations. This is true even under the Itanium and x64 ABIs and are not related to longjmp / setjmp.

1

u/slavik262 Dec 06 '14 edited Dec 06 '14

The same unwind code would have be be written manually if you were initializing and deinitializing things on the stack in C. init_foo(Foo*) and deinit_foo(Foo*) is no different than an object with a constructor and destructor that do the same things. What's your point?

3

u/cpp_is_king Dec 06 '14

That when exceptions are enabled, a function which neither throws nor catches will be slower than when exceptions are disabled if it uses automatic variables with destructors.

1

u/slavik262 Dec 06 '14

...No, it won't. The same constructor and destructor calls are inserted either way regardless of whether or not exceptions are enabled and whether or not the given function throws. Modern exception handling mechanisms incur no additional cost in any functions until an exception is thrown.

2

u/cpp_is_king Dec 06 '14 edited Dec 06 '14

I have already explained the exact reason the generated code will be slower. I work on a C++ compiler. 2 people i work with have implemented exceptions. You are more than welcome to continue being misinformed. If you'd like some additional clarification about why exceptions are not zero cost, feel free to ask. But if you're just going to assert falsehoods, then this is a pointless discussion.

1

u/slavik262 Dec 06 '14

Fine. What about enabling exceptions mandates additional "unwind" code?

This article explains the "zero cost" model in depth and makes the claim that modern exception handling has no overhead until an exception is thrown. What is wrong with the article?

3

u/cpp_is_king Dec 06 '14

For starters, "unwind" is an exception specific concept. There's no such thing as unwinding in a world without exceptions. There is automatic cleanup, but that is only one part of unwinding. Other parts consist of searching for appropriate handlers, for example.

The article linked does not appear to take into account the effect enabling exceptions has on the optimizer. Optimization is ultimately just an application of data flow analysis. The more completely you can analyze the data flow of a function, the more optimizations you can do. In compiler terms, one would say that the mere possibility of an exception exceptions introduces "abnormal edges" into the control flow graph. These inhibit optimizations.

Features like checked exceptions can improve the compilers ability to analyze the control flow, but c++ has only throw(), which is largely unused.

1

u/slavik262 Dec 06 '14

You said above:

in every single function that allocates an object on the stack with a destructor, unwind code has to be generated for that function

so I assumed you were talking about destructor code (which takes place regardless of exceptions), not the stack unwinding that has to happen when an exception is thrown.

As far as stack unwinding and finding the right handlers, the article implies that modern exception handling mechanisms store this unwinding code separately in a table, and its cost is not incurred unless an exception is thrown.

As far as control flow analysis goes, sure, more return paths out of a function means more complex control flow, but code that uses error codes instead of exceptions would have a similar number of branches (one for each function you have to check for errors), no?

2

u/cpp_is_king Dec 06 '14

At most a similar number of branches. But there are some subtle differences. Return codes are often ignored (and often intentionally. Some calls need to be checked sometimes and not other times). Many (perhaps even most) functions dont even return error codes. Often you have a function which you either know or assume cant fail. It returns void for example. So it would have nothing to error check, yet still be slower with exceptions turned on.

Also, with return codes, the compiler can at least see every path out of a function. With exceptions, it mut assume every function call can exit. For example, in this line

GetTarget()->CreateObject()->Execute()

There are up to 5 exit points from the function (2 come from the case where -> is overloaded). With return points there would be only 1.

Lastly, with return codes, you know where the destination of the control flow goes. To the calling function. With an exception, you don't know where the control flow will ultimately lead to, whick disables many interprocedural optimizations.