Do you use a garbage collector?

L

Lloyd Bonafide

I followed a link to James Kanze's web site in another thread and was
surprised to read this comment by a link to a GC:

"I can't imagine writing C++ without it"

How many of you c.l.c++'ers use one, and in what percentage of your
projects is one used? I have never used one in personal or professional
C++ programming. Am I a holdover to days gone by?
 
D

Daniel Kraft

Lloyd said:
I followed a link to James Kanze's web site in another thread and was
surprised to read this comment by a link to a GC:

"I can't imagine writing C++ without it"

How many of you c.l.c++'ers use one, and in what percentage of your
projects is one used? I have never used one in personal or professional
C++ programming. Am I a holdover to days gone by?

I can't tell what professional C++ programming is about or similar, but
I've also never used a GC for any of my projects (and those included
ones with several months of development time and non-trivial structure).

I've never seen the need for it and in fact am rather happy if I can do
the memory management explicitelly rather than by a GC which feels
cleaner to me (BTW, I also only once needed to do reference-counting, so
for those things the "refcounting requires more time than GC"-argument
is out).

Daniel
 
J

Juha Nieminen

Lloyd said:
How many of you c.l.c++'ers use one, and in what percentage of your
projects is one used? I have never used one in personal or professional
C++ programming. Am I a holdover to days gone by?

I have never used a GC for C++, yet in none of my C++ projects
(professional or hobby) in the last 5+ years have I had a memory leak. I
often use tools such as valgrind and AQTime to check for memory leaks,
and they have yet to report any leak.

There just exists a *style* of programming in C++ which very naturally
leads to encapsulated, clean and safe code. (This style is drastically
different from how, for example, Java programming is usually done.)

One situation where GC *might* help a bit is in efficiency if you are
constantly allocating and deallocating huge amounts of small objects in
tight loops. 'new' and 'delete' in C++ are rather slow operations, and a
well-designed GC might speed things up.
However, part of my C++ programming style just naturally also avoids
doing tons of news and deletes in tight loops (which is, again, very
different from eg. Java programming where you basically have no choice).
Thus this has never been a problem in practice.

Even if I some day stumble across a situation where constant
allocations and deallocations are impacting negatively the speed of a
program, and there just isn't a way around it, I can use a more
efficient allocator than the default one used by the compiler. (I have
measured speedups to up to over 8 times when using an efficient
allocator compared to the default one.)
 
P

Pascal J. Bourguignon

Juha Nieminen said:
I have never used a GC for C++, yet in none of my C++ projects
(professional or hobby) in the last 5+ years have I had a memory leak. I
often use tools such as valgrind and AQTime to check for memory leaks,
and they have yet to report any leak.

There just exists a *style* of programming in C++ which very naturally
leads to encapsulated, clean and safe code. (This style is drastically
different from how, for example, Java programming is usually done.)

One situation where GC *might* help a bit is in efficiency if you are
constantly allocating and deallocating huge amounts of small objects in
tight loops. 'new' and 'delete' in C++ are rather slow operations, and a
well-designed GC might speed things up.
However, part of my C++ programming style just naturally also avoids
doing tons of news and deletes in tight loops (which is, again, very
different from eg. Java programming where you basically have no choice).
Thus this has never been a problem in practice.

Even if I some day stumble across a situation where constant
allocations and deallocations are impacting negatively the speed of a
program, and there just isn't a way around it, I can use a more
efficient allocator than the default one used by the compiler. (I have
measured speedups to up to over 8 times when using an efficient
allocator compared to the default one.)

Now, on another level, I'd say that the problem is that this safe
style you must adopt in C++ to avoid memory leaks is a burden that
forces you to spend your energy in a sterile direction.

More dynamic languages, with a garbage collector, are liberating your
mind, so your free neurons can now think about more interesting
software problems (like for example, AI, or providing a better user
experience, etc).
 
N

Noah Roberts

Lloyd said:
I followed a link to James Kanze's web site in another thread and was
surprised to read this comment by a link to a GC:

"I can't imagine writing C++ without it"

How many of you c.l.c++'ers use one, and in what percentage of your
projects is one used? I have never used one in personal or professional
C++ programming. Am I a holdover to days gone by?

I use RAII. I've had memory leaks but it's usually due to some third
party application I didn't understand adequately. I was never able to
figure out why AQTime was reporting large chunks of memory loss in my
use of the sqlite3 library for example. We abandoned its use.
 
M

Matthias Buelow

Pascal said:
More dynamic languages, with a garbage collector, are liberating your
mind, so your free neurons can now think about more interesting
software problems (like for example, AI, or providing a better user
experience, etc).

Hey now, what's this nonsense against the pure joy if your program looks
totally cryptic, proves that you know the fine details of an 800 pages
thick standard, and runs an infinite loop in under 3 seconds...
 
J

Juha Nieminen

Pascal said:
Now, on another level, I'd say that the problem is that this safe
style you must adopt in C++ to avoid memory leaks is a burden that
forces you to spend your energy in a sterile direction.

I strongly disagree. This style of programming almost automatically
leads to a clean, abstract, encapsulated design, which is only a good thing.

Programmers using languages with GC (such as Java) might not need to
worry about where their dynamically-allocated memory is going, but
neither do I, in most cases. I even dare to claim that at least in some
cases this style of modular programming produces cleaner, simpler, more
understandable and in some cases even more efficient code than a
"mindlessly use 'new' everywhere" style of programming.

I honestly don't feel that I need to put any extra effort to produce
this kind of safe and clean code. Given that I usually like the end
result as a design, even if I have to put that bit of extra effort it's
totally worth it.
More dynamic languages, with a garbage collector, are liberating your
mind, so your free neurons can now think about more interesting
software problems (like for example, AI, or providing a better user
experience, etc).

I have my doubts that this "liberating" style of programming somehow
automatically leads to clean, modular and abstract designs. All the
contrary, I would even claim that at least in some cases it leads to the
opposite direction ("reckless programming").
 
J

Juha Nieminen

Razii said:
In C++, each "new" allocation request
will be sent to the operating system, which is slow.

That's blatantly false.

At least with the memory allocation logic in linux's libc (and I guess
in most other unix systems as well) the only system call that is made is
brk(), which increments the size of the heap. AFAIK this is done in
relatively large chunks to avoid every minuscule 'new' causing a brk()
call. Thus the OS is called relatively rarely. The actual memory
allocation of the blocks allocated with 'new' (or 'malloc') are done by
the routine in libc, not by the OS.

I don't know how Windows compilers do it, but I bet it's something
very similar to this.

I don't see how this is so much different from what Java does.
 
J

Jerry Coffin

Howeever, Java allocates new memory blocks on it's internal heap
(which is allocated in huge chunks from the OS). In this way, in most
of the cases it bypasses memory allocation mechanisms of the
underlying OS and is very fast. In C++, each "new" allocation request
will be sent to the operating system, which is slow.

I know of one library implementation for which this was true (one of the
first 32-bit versions of Visual C++), but it's generally false. Even in
that version of that compiler, the library _contained_ code to allocate
from the OS in large blocks, and then do sub-allocations out of that
block. For reasons known only to Microsoft, that code was disabled by
default.

There is still typically some difference in speed, at least when a
compacting GC is used. Specifically, since this keeps the free memory in
the heap in one large chunk, allocation is done about like on a stack.

By contrast, with a manually managed heap that doesn't do compaction,
you end up with free blocks interspersed with blocks in use. In a really
traditional design, the allocator might round the requested size up to
the next multiple of, say, 16 bytes, and then walk through the list of
free blocks to find one large enough to satisfy the request. When it
found one, it would check the current size of the block, and if it
exceeded the requested size by at least some margin, it would split the
block in two, satisfying the request with one and placing the other back
on the free list.

More recent designs do things like rounding requests to powers of two
(starting from 16 or so), and keeping a separate free list for each
size. Once you've rounded up the size, you search in the correct list,
and either a block is there, or it isn't. If it isn't, you go to the
next larger block size, split that block in half, satisfy the request
with one half, and place the other half on the previously-empty free
list for its new size.

The former version can be quite slow, especially if there are a lot of
free blocks in the system. As you can probably guess, the latter can
improve speed quite a bit.
 
A

Arne Vajhøj

Razii said:
Let's test this about the keyword "new" and tight loops. Because in
most cases Java allocates new memory blocks on it's internal heap and
bypasses memory allocation mechanisms of the underlying OS, the
keyword "new" doesn't mean the same thing that it does in C++, where
each "new" allocation request is sent to the operating system, which
is very slow.

I can not imagine any C++ runtime that makes an operating system
call for each new.

The runtime allocates huge chunks from the OS and then manage
it internally.

Arne
 
M

Mike Schilling

Razii said:
Testing the keyword "new"

Time: 2125 ms (C++)
Time: 328 ms (java)

Explain that.

The C++ allocator is less efficient. There's a big difference between that
and "makes an OS call for each allocation". In fact, given how highly
optimized Java allocators are these days, the fact that C++ is taking less
than 7 times as long proves that it's *not* making an OS call.
 
J

Jerry Coffin

[ ... ]
You are incorrect. Each call into "new" will "most-likely" be "fast-pathed"
into a "local" cache. On multi-threaded systems, this "cache-hit" can even
be accomplished without using _any_ interlocked RMW or memory barrier
instruction(s). That is, the cache would be per-thread. Some may think that
if a cache is per-thread then a thread will not be able to free something
unless it allocated it. This is false line of thinking. There are different
algorithms that can solve this, one of them can be found here:

http://groups.google.com/group/comp.arch/browse_frm/thread/24c40d42a04ee855

Hoard and StreamFlow also use per-thread heaps.

Most of the problem seems to be that people think in terms of a heap
having a single free list, so all other threads have to contend for
access to that free list.

A trivially simple design is for each thread to have its own heap, and
each heap to have one free list for every other thread in the system.
Each other thread can then free blocks to any heap with minimal
possibility of contention (specifically, with the thread whose heap it's
freeing memory TO, and then only if it happens while that thread is
grabbing that free list to coalesce its memory with the other free
blocks.

This contention can normally be reduced to a single atomic exchange to
free memory and another to grab memory for coalescing. Specifically,
consider a structure something like:

struct free_block {
void *next;
size_t size;
size_t origin_threadID;
char payload[0];
};

Technically, that's not legal in C++, but I supect you get the idea.
Let's also assume each thread's heap has a vector of free-lists,
something like:

typedef std:vector<free_block *> free_list;

And we have a vector of free lists, one per thread:

std::vector<free_list> free_lists;

In that case, freeing a block looks something like:

void free_a_block(void *block) {
free_block *f = ((char *)block)-sizeof(free_block);
f->next = f;
size_t heap = f->origin_threadID;
size_t my_threadID = get_current_thread_ID();

atomic_exchange(f->next, free_lists[heap][my_thread_ID]);
}

I.e. we put this block's address into its own 'next' field, and then
exchange that with the pointer to the head of our free list in the
appropriate heap.

If you want to eliminate even the last vestige of a wait, you can go one
step further: have each free list contain _two_ free-list heads instead
of one. At any given time, the owning thread may be using only one of
those. You use a mutex that waits on both, and one of them will always
be free. You update it, and free the mutex. The design isn't lock free,
but it guarantees that you'll never have to wait for the lock to take
place -- both mutexes will be free most of the time, but at least one
will be all the time. The wait for the mutex just determines _which_ one
is free right now.

Of course, in reality that design is generally overkill -- the chances
that all threads will be freeing memory on a single thread's heap at any
given time is minmal to say the least. If we don't mind introducing the
possibility of some waiting, it's pretty trivial to automatically
balance between speed and memory usage -- we start with a single free
list in each heap (and a mutex associated with each free list).

When we want to free a block, we wait on the set of mutexes for a heap,
with a fairly short timeout. If the wait succeeds, we free the memory
and go on our way. If the wait fails, we signal the thread that a wait
failed. After waits fail some number of times, the thread reacts by
increasing the number of available free lists. Both the length of the
wait and the number of failures can be adjusted to balance between
performance and memory usage.
 
J

Jerry Coffin

I followed a link to James Kanze's web site in another thread and was
surprised to read this comment by a link to a GC:

"I can't imagine writing C++ without it"

How many of you c.l.c++'ers use one, and in what percentage of your
projects is one used? I have never used one in personal or professional
C++ programming. Am I a holdover to days gone by?

I have used one, but not in quite a while. IIRC, it was in the late '90s
or so when I tried it. I was quite enthused for a little bit, but my
enthusiasm slowly faded, and I think it's been a couple of years or so
since the last time I used it at all. I've never really made any kind of
final decision that I'll never use it again, or anything like that, but
haven't done anything for which I'm convinced it would be at all likely
to provide any real advantage either.
 
A

Arne Vajhøj

Razii said:
Testing the keyword "new"

Time: 2125 ms (C++)
Time: 328 ms (java)

Explain that. What I am doing different in java than in c++? Code
below..

If you can prove that the only way to write inefficient code is to make
OS calls, then you have made your point.

But as everyone knows it is not, so your argument is completely bogus.

Arne
 
I

Ian Collins

Razii said:
Let's test this about the keyword "new" and tight loops. Because in
most cases Java allocates new memory blocks on it's internal heap and
bypasses memory allocation mechanisms of the underlying OS, the
keyword "new" doesn't mean the same thing that it does in C++, where
each "new" allocation request is sent to the operating system, which
is very slow.

Creating 10000000 new objects with the keyword 'new' in tight loop.
If a C++ programmer had to do this in the most efficient way possible,
he/she would use a custom allocator.
int main(int argc, char *argv[]) {

clock_t start=clock();
for (int i=0; i<=10000000; i++) {
Test *test = new Test(i);
if (i % 5000000 == 0)
cout << test;
}

Leaks 10000000 objects.
for (int i=0; i<=10000000; i++) {
Test test = new Test(i);
if (i % 5000000 == 0)
System.out.println (test);
}

Does the Java allocator/GC combination recycle the objects in the loop?
 
R

Roedy Green

Creating 10000000 new objects with the keyword 'new' in tight loop.

All Java has to do is add N (the size of the object) to a counter and
zero out the object. In C++ it also has to look for a hole the right
size and record it is some sort of collection. C++ typically does not
move objects once allocated. Java does.

In my C++ days, we used NuMega to find leaks, objects that were
allocated butw never deleted even after there were no references to
them. We never got anywhere near nailing them all. With Java this is
automatic. You can't make that sort of screw up, though you can
packrat. See http://mindprod.com/jgloss/packratting.html
 
M

Mike Schilling

Razii said:
What argument is bogus? My point was that the keyword 'new' is much
faster in java because JVM allocates new memory blocks on it's
internal heap and bypasses memory allocation mechanisms of the
underlying OS.

And you're wrong, as has been demonstrated repeatedly. There's no
point trying to explain this any further.
 
I

Ian Collins

Razii said:
That is not the topic. The topic is how the keyword "new" behaves.
The change was fair, it compares idiomatic language use.

Anyhow, a if you are looking at the keyword new, as simple change to the
Test class,

class Test
{
static const unsigned nItems(10000001);

public:
Test (int c = 0) : count(c) {}
int count;
void*

operator new( size_t n ) throw( std::bad_alloc )
{
static Test* items = static_cast<Test*>(malloc(sizeof *items * nItems));
static unsigned next(0);

if( next >= nItems ) throw std::bad_alloc();

return &items[next++];
}

void operator delete( void* v ) throw() {}
};

runs in 90mS on my box (compared to ~940mS with default new).

As other's have said, something that's idiomatic Java isn't necessarily
idiomatic C++. Sam pointed you to the idiomatic C++ code, my example
shows what a C++ programmer can do when forced to use an unnatural idiom.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top