Re: Oozing poison
Ian Collins <ian-news@hotmail.com> writes:
On 01/29/12 10:31 AM, Scott Lurndal wrote:
Miles Bader<miles@gnu.org> writes:
scott@slp53.sl.home (Scott Lurndal) writes:
The catch is also the reason for all the extra code, constructing and
destructing a temporary std::exception object. The actual exception
handling part of the code is this bit:
Yet creating and destroying a temporary std::exception object also counts in
terms of both extra cycles and code footprint. Both of which impact
performance (generally negatively).
Er, exceptions are intended to be _exceptional_.
If some exception occurs so often that it has a measurable performance
impact, it probably shouldn't be an exception.
The exception never occurs (i.e., I never run out of memory). It's the
added code to handle the exception and it's effect on a (very) hot codepath
that causes the 4% hit in performance. That is the only change in the
codebase, and I ran the benchmark three times with each flavor (try/catch/new
vs. malloc). I have to admit that I was surprised by the difference and I'm
going to take a look at the actual codepaths and do some profiling to see why
adding a try/catch clause has such a large effect, when the exception is never
taken.
How about new (without the exceptions) compared to malloc? new is doing
more work than malloc, one reason I suggested a specialised allocator.
I've tried using new (sans catch) and malloc both. The difference there is
pretty much in the noise. I've been planning on trying the gcc option -fcheck-new
with the 'new' to handle the case where new returns null (to prevent the constructor
from running) and compare that with malloc; this will add a test and
branch to the hot path, I suspect. If I use new (nothrow), will that still call
the constructor (and SIGSEGV) if the new returns NULL?
I ported a pool allocator over the weekend that can be used with an overloaded
new operator
void *operator new(size_t, c_pool *p) { return p->alloc(); }
The pool class uses ::mmap(MAP_ANONYMOUS) to get pages which are then subdivided
into appropriately sized chunks (16-byte aligned for efficiency on 64-bit
architectures).
c_pool *pool;
pool = new c_pool("Class Objects", sizeof(c_class), 100); // At init time
...
object = new (pool) c_class(); // at run time
This is highly efficient in the cases where you know a priori that you'll
never run out of pool elements (sans a coding error, of course).
A downside is you can't use delete but rather must pool->free(object).
scott