Jump to page: 1 2 3
Thread overview
Raymond Chen's take on so-called zero cost exceptions
Feb 28, 2022
Walter Bright
Feb 28, 2022
H. S. Teoh
Mar 01, 2022
deadalnix
Mar 01, 2022
Elronnd
Mar 01, 2022
Elronnd
Mar 01, 2022
deadalnix
Mar 01, 2022
Elronnd
Mar 01, 2022
deadalnix
Feb 28, 2022
Guillaume Piolat
Mar 01, 2022
H. S. Teoh
Mar 01, 2022
Araq
Mar 01, 2022
deadalnix
Mar 01, 2022
deadalnix
Mar 01, 2022
Adam D Ruppe
Mar 01, 2022
forkit
Mar 01, 2022
meta
Mar 01, 2022
forkit
Mar 01, 2022
meta
Mar 01, 2022
forkit
Mar 01, 2022
IGotD-
Mar 01, 2022
meta
Mar 01, 2022
meta
Mar 01, 2022
forkit
Mar 01, 2022
Guillaume Piolat
February 28, 2022
"The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generation: Before performing any operation that could potentially throw an exception, the compiler must spill any object state back into memory if the object is observable from an exception handler. (Any object with a destructor is observable, since the exception handler may have to run the destructor.)
Similarly, potentially-throwing operations limit the compiler’s ability to reorder or eliminate loads from or stores to observable objects because the exception removes the guarantee of mainline execution."

https://devblogs.microsoft.com/oldnewthing/20220228-00/?p=106296

This is what I've been saying.
February 28, 2022
On Mon, Feb 28, 2022 at 12:33:17PM -0800, Walter Bright via Digitalmars-d wrote:
> "The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generation: Before performing any operation that could potentially throw an exception, the compiler must spill any object state back into memory if the object is observable from an exception handler. (Any object with a destructor is observable, since the exception handler may have to run the destructor.) Similarly, potentially-throwing operations limit the compiler’s ability to reorder or eliminate loads from or stores to observable objects because the exception removes the guarantee of mainline execution."
> 
> https://devblogs.microsoft.com/oldnewthing/20220228-00/?p=106296
> 
> This is what I've been saying.

How is this any worse than explicitly checking for error codes? Isn't the optimization situation of:

	MyObj obj;
	mayThrow();
	...
	// obj.dtor called

different from:

	MyObj obj;
	if (mayError() == ERROR)
		goto END;
	...
	END:
	// obj.dtor called

?


T

-- 
Computers aren't intelligent; they only think they are.
February 28, 2022
On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:
> "The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generation
In my C++ years, exceptions in combination with RAII were the best barrier of defense against leaks and bugs in error paths. Also they would allow C++ constructors to fail, and error codes didn't. Worse, with error codes, people routinely conflated runtime errors and unrecoverable errors. Performance is imo a false concern here since 1. either code that should be fast is devoid of eror handling in the first place 2. correctness of whole codebases is at stake 3. you can always make a correct program faster, but there will be noone to realize the incorrect program is incorrect.
March 01, 2022
On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:
> "The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generation: Before performing any operation that could potentially throw an exception, the compiler must spill any object state back into memory if the object is observable from an exception handler. (Any object with a destructor is observable, since the exception handler may have to run the destructor.)
> Similarly, potentially-throwing operations limit the compiler’s ability to reorder or eliminate loads from or stores to observable objects because the exception removes the guarantee of mainline execution."
>
> https://devblogs.microsoft.com/oldnewthing/20220228-00/?p=106296
>
> This is what I've been saying.

There are a bit more to this though. First the exception ABI on windows is 100% atrocious and impose way more constraints than the usual libunwind based ones.

But most importantly, second, the argument is fallacious, as comparing code with and without exception makes no sense. What you want to compare is the code that uses exception vs the code that uses *something else* for error handling.

While that something else is almost always more expensive than exception on the non throwing path. The numbers from the original paper show this.
March 01, 2022
On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:
> https://devblogs.microsoft.com/oldnewthing/20220228-00/?p=106296

"Zero-cost exceptions are great"

> This is what I've been saying.

March 01, 2022
On Monday, 28 February 2022 at 21:39:03 UTC, H. S. Teoh wrote:
> How is this any worse than explicitly checking for error codes? Isn't the optimization situation of:
>
> 	MyObj obj;
> 	mayThrow();
> 	...
> 	// obj.dtor called
>
> different from:
>
> 	MyObj obj;
> 	if (mayError() == ERROR)
> 		goto END;
> 	...
> 	END:
> 	// obj.dtor called
>
> ?
>
>
> T

obj can be kept in a register in the goto case, it cannot in the exception case. So you'll have a couple extra load/store vs extra branches.
March 01, 2022
On Tuesday, 1 March 2022 at 01:39:05 UTC, deadalnix wrote:
> obj can be kept in a register in the goto case, it cannot in the exception case. So you'll have a couple extra load/store vs extra branches.

Nope, it can be kept in a register in the exception case too.  See: https://godbolt.org/z/zP1P3xvr3

The pushes and pops of RBX are necessary in both cases, because it is a caller-saved register.  (And, well, they are also necessary for stack alignment, so be wary of taking too many conclusions from a microbenchmark.)  Beyond that, note that the exception-using versions have fewer instructions in the hot path, fewer branches, and exactly the same number of memory accesses as the manually-checking versions.

(GCC generates better code, but clang's implementation of 'h' is more representative, which is why I show both; *usually* you can't run both the happy path and the sad path branchlessly.)
March 01, 2022
On Tuesday, 1 March 2022 at 05:06:22 UTC, Elronnd wrote:
> The pushes and pops of RBX are necessary in both cases, because it is a caller-saved register

callee-saved, of course.
February 28, 2022
On Mon, Feb 28, 2022 at 11:01:46PM +0000, Guillaume Piolat via Digitalmars-d wrote:
> On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:
> > "The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generation
>
> In my C++ years, exceptions in combination with RAII were the best barrier of defense against leaks and bugs in error paths. Also they would allow C++ constructors to fail, and error codes didn't. Worse, with error codes, people routinely conflated runtime errors and unrecoverable errors.  Performance is imo a false concern here since 1. either code that should be fast is devoid of eror handling in the first place 2. correctness of whole codebases is at stake 3. you can always make a correct program faster, but there will be noone to realize the incorrect program is incorrect.

IIRC, we had this discussion before some time ago, and Adam pointed out that in D's early days it used to have its own exception-handling implementation that involved passing exception status via a spare register, supposedly faster than the baroque C++-style libunwind / (whatever the Windows equivalent is) implementation. But later on, in the name of C++ compatibility, that got replaced with libunwind / Windows EH.

My point is, if you're dealing with code that may fail, you've got to handle the error case *somehow*. Whether it's via a return code, or libunwind, or passing some status in a spare register that the compiler automatically inserts a check for.  The syntax is really irrelevant. Whether you write:

	void mayFail() { throw new Exception(""); }

	void myFunc() {
		MyObj obj;
		mayFail();
		...
		// obj.dtor invoked
	}

or:

	int mayFail() { return ERROR; }

	void myFunc() {
		MyObj obj;
		if (mayFail() == ERROR) // returns error status
			goto EXIT;
		...
	EXIT:
		// obj.dtor invoked
	}

the semantics are essentially the same.  If one implementation of exceptions is not as performant, why can't we switch to a more efficient implementation?  After all, the compiler can, in theory, implement `throw` in the first code snippet above by lowering it into the equivalent of the second code snippet. Say, by using a spare register to indicate the error (if mayFail returns a value besides its error status).

The actual implementation of how exceptions are handled isn't really tied to the surface syntax of the program.  One way or another you need to handle the error condition somehow; what about exceptions makes it fundamentally less efficient than handling an error code?

If error codes are somehow fundamentally more efficient, why can't the compiler just rewrite throwing functions into functions that return error codes, with the compiler inserting error code checks into the caller as needed?


T

-- 
Why waste time learning, when ignorance is instantaneous? -- Hobbes, from Calvin & Hobbes
March 01, 2022
On Tuesday, 1 March 2022 at 06:44:05 UTC, H. S. Teoh wrote:
> If error codes are somehow fundamentally more efficient, why can't the compiler just rewrite throwing functions into functions that return error codes, with the compiler inserting error code checks into the caller as needed?

Exactly. It's entirely feasible. And it is what Swift does: https://www.mikeash.com/pyblog/friday-qa-2017-08-25-swift-error-handling-implementation.html

(It's not universally faster than table based exceptions but it seems preferable for embedded devices.)
« First   ‹ Prev
1 2 3