My memory problems are over
axilmar

I've decided that I had enough of memory problems with c++...so no more manual memory management for me. I will use Boehm's gc from now on, which is extremely good and very very fast.

Why am I saying this? well, I've just saw another memory problem topic in the forum, and I've thought I'd share my two bits of wisdom.

People, use garbage collection! even in games! at this day and age, computers are fast enough to handle it, and the collector is very mature. Thousands of objects can be collected within a few milliseconds.

GullRaDriel

glib ftw

Goalie Ca

I'll stick to using RAII and wrapper classes such as smart pointers. The only hole left from that are cyclical dependencies (which can technically be addressed by a little thinking and good use of weak pointers). Most of the time either people share pointers or someone owns a pointer.

All of these are examples of objects which own memory and cleanup automatically when they get destructed. They die when they go out of scope.

axilmar
Quote:

I'll stick to using RAII

Why not use gc? gc has numerous advantages over reference counting.

kazzmir

Part of my research at school is looking at precise garbage collectors with C code. I have a tool that converts normal C code to use a precise garbage collector, if you want to try it.

Jeff Bernard

I don't need garbage collection... it's not that hard to delete a pointer or destroy a bitmap...

SiegeLord

Lack of garbage collection is still faster than the presence of garbage collection. All modern OS's (AFAIK) free the application's memory after the application closes. Things like games run for a couple of hours at a time on average, so it does not matter if they leak. I want my programs to be fast, so I purposefully leak many of my objects. I take care not too create too many, and it all works out in the end.

If you are creating objects every logic step you have a problem with the design, the solution is to alter your design, not to add a blind garbage collector I.e. there is no need for a garbage collector if there is no garbage.

kazzmir
Quote:

I want my programs to be fast, so I purposefully leak many of my objects.

Remind me never to play any of your games.

SiegeLord

If an object is created only once there is no difference whether it is freed or not. If a program leaks and it's leaks do not get worse over time there is absolutely no difference for the end user, since in the end the program uses the same amount of memory, but because of a lack of an explicit gc it runs faster.

Karel Kohout

I'm depressed. I expected a revolutionary idea improving your memory (and helping me study for exams at 9 a.m., twelve hours from now including sleeping and 1h in the underground) and all you suggest is using substandard obscure compiler :-/

kazzmir

Yes, certainly for singleton objects you don't have to free them. I probably dont reclaim those objects myself. But you made it sound like you leak various small objects because its easier to not deal delete them manually. Over time this might cause problems.

As a reasonable compromise you could just run the gc when the current level ends or some other normal pause in the game occurs. This is pretty much how I deal with all the objects in my latest game. Allocate things during the game and deallocate everything at the end of each level.

Goalie Ca
Quote:

Why not use gc? gc has numerous advantages over reference counting.

Like what? Memory pooling? I can do that with precision using one of the many simple libraries. The only advantage i see is that it deals with cycles.

One of the problems with GC's is that you go from manually releasing memory to manually releasing resources. In a gc environment you never know when an object is going to be disposed of. This kills all hope of using the destructor to close the file handle etc.

edit: doing even get me going on about real-time collectors. What a load of crap.

edit2: SiegeLord.. WTF!

23yrold3yrold
Quote:

SiegeLord.. WTF!

I'll second that. Memory leaks have cause a few of my programs to crash before I caught them, running fine on my system but not on older ones. Proper allocation and deallocation are staples of proper programming; to say "fsck it" and just go is madness.

Quote:

If a program leaks and it's leaks do not get worse over time there is absolutely no difference for the end user

You need to recognize how colossal this "if" is.

Vanneto

Its not hard to create/delete object in small/medium sized projects. But in projects like games or browsers ( Firefox ) it can be a problem.

kazzmir
Quote:

One of the problems with GC's is that you go from manually releasing memory to manually releasing resources. In a gc environment you never know when an object is going to be disposed of. This kills all hope of using the destructor to close the file handle etc.

Its called a finalizer. http://www.hpl.hp.com/personal/Hans_Boehm/gc/finalization.html

axilmar
Quote:

Like what? Memory pooling? I can do that with precision using one of the many simple libraries. The only advantage i see is that it deals with cycles.

Not only it deals with cycles, but:

1. it's way faster than reference counting.
2. more readable code.
3. faster and easier to write for.
4. works with C.
5. works in a multithreaded environment.

Quote:

One of the problems with GC's is that you go from manually releasing memory to manually releasing resources. In a gc environment you never know when an object is going to be disposed of. This kills all hope of using the destructor to close the file handle etc.

You don't have that problem in C++. You can continue declaring your instances on the stack, i.e. use RAII as you see fit.

In fact, one of the reasons I'm sticking to C++ is RAII (and templates, of course).

Quote:

doing even get me going on about real-time collectors. What a load of crap.

IBM disagrees with you!!!

Goalie Ca

Yeh I know about finalizers. They still aren't perfect. Nothing ever is though. I first learned all about them when dealing with dispose and finalize in c#.

GC's are not "way faster" than reference counting. Maybe gc's built upon reference counting are slower though.
Readable code.. if you consider boehm extensions readable and pointers not.
Faster and easier to write for.. yes and no
Yes gc's do work with c. Smart pointers require c++ to be syntactically clean. Otherwise there are a lot of winapi style dealings with structs and manual counting. (i think that's how winapi deals with it. i might be confusing them with another)
multithreaded.. Boost's pointers are thread safe. What i haven't seen is a concurrent GC.

axilmar
Quote:

GC's are not "way faster" than reference counting.

They are. Assigning a reference counted pointer results in dozens of instructions.

Quote:

Readable code.. if you consider boehm extensions readable and pointers not.

Boehm's gc does not have any extensions in the language. Instead of using malloc, you use GC_malloc.

Quote:

multithreaded.. Boost's pointers are thread safe.

And slow, because they have to InterlockedIncrement/Decrement ref counts.

Quote:

What i haven't seen is a concurrent GC.

Boehm's GC is concurrent: it will start N threads for marking and collection, where N is the number of available CPUs, if compiled with PARALLEL_MARK.

SiegeLord
Quote:

You need to recognize how colossal this "if" is.

If your design is good it is not colossal at all. If a program has many leaks it means that you are using heap allocations often. Heap is not meant to be used in that way, you are supposed to allocate and deallocate it very very sparingly. A good design does not need gc. Specifically, imo, garbage collection encourages lazy coders.

kazzmir
Quote:

Heap is not meant to be used in that way, you are supposed to allocate and deallocate it very very sparingly.

??? Where do you get this stuff?

Vanneto

Game programming All in One!

Edgar Reynaldo

I've implemented a simple memory manager class with vectors of different common pointer types that lets me deallocate them all when the object goes out of scope. Not so useful for cross function purposes , but handy enough to use when there are heavy or extensive memory allocations in a single frame. Still have to write one line for allocation and another to add it to the classes vector , but I can just write the lines successively and be done with it.

Goalie Ca

Once i get this paperwork done i will sit down and study the papers on the boehm gc. Admittedly i haven't looked at these things in quite a while. Mostly if i want "memory woes gone" then i use a language that has a gc and has other nice things as well. Auto_ptr's have ownership and they are fast. Only penalty is in assignment. Then shared_pointers only have penalty in assignment as well.

And sieglord.. ya if you are frequently allocating and deallocating then maybe you want to set up a memory pool. Memory pools have very very fast allocations.

HoHo

I wonder if I'm missing something but why do people use that much manual memory management? I have written a C++ client/server application that have around 10kloc combined and about ten or so new blah in it with each of those called once per program lifetime (some classes cannot live in stack). There are a lot of stuff constantly changing and I've yet to see a leak even with weeks of uptime. I simply use STL* a lot and it works great!

*) Those programs do not use QT, with QT things would be even simpler even though there would be a lot more manual memory allocations there. QT FTW ;)

Carrus85

Garbage collection is great, in theory, as long as you keep in mind some drawbacks:

  • Encourages lazier programming in general (good for quick hack-up programs, bad when the runtime performance of an algorithm is very, very important. Also encourages "static abuse" in a lot of GC-able languages (for example, concatenating two strings together in Java creates a whole new string, because strings are immutable; major performance bottleneck for those who don't watch their step).

  • Adds a degree of non-determinism in program runtime (e.x. Writing a ChessAI in C# can get kinda dicey when you have a hard turn time limit.). (Referencing counting also has this problem, but not as bad. Incremental Garbage Collectors also tend to avoid this problem.)

  • Sometimes you just want control of the memory allocations.

  • Doesn't work with RAII (if you must allocate everything on the heap, ala java). And no, finalizers are not a substitute for RAII due to their non-deterministic execution; you know the finalizer will run, but you don't know when it will run, which can be a huge problem if you expect it to run immediately after going out of scope. (Thus, you end up having to finally blocks and other garbage to clean up after yourself, which isn't as clean as RAII.)

Basically: If the program has some hard deadlines, requires as much efficiency as possible (say, your kernel or some math function that is called thousands of times), or is highly interactive, then GC's should be used with extreme caution or avoided entirely. GC's work pretty much everywhere else, though.

Now, that given, the description of Boehm's gc via GC_malloc, it should be possible to just globally override new and delete and use them (assuming GC_malloc doesn't take any other strange arguments or have screwy side effects, as well as a GC_free existing somewhere). This would give you a really interesting benefit, in C++ in particular; you could use RAII where you want, use regular old new for everything else, and get garbage collection for free (in theory). I'd have to look up specifics and run some tests, though. :)

HoHo
Quote:

say, your kernel or some math function that is called thousands of times

For performance reasons those things should avoid dynamic memory as plague anyway or at least use object pools

axilmar
SiegeLord said:

Heap is not meant to be used in that way, you are supposed to allocate and deallocate it very very sparingly.

Not if the application is big and requirements change often.

Goalie Ca said:

Once i get this paperwork done i will sit down and study the papers on the boehm gc.

It's very clever. It takes into advantage the virtual memory system, and allocates/deallocates memory in blazing speed. In my machine, 100,000 objects are freed in less than a millisecond. With 10 threads allocating 100,000 objects, it takes less than a second to finish the program, and my CPU is not even dual core.

Goalie Ca said:

Mostly if i want "memory woes gone" then i use a language that has a gc and has other nice things as well.

Well, it all depends on the requirements of the software. The new project I am about to do must be written in c++, and the deadlines are very strict. I spend 4 months debugging the previous project (120,000 lines of c++ code), and it still have bugs, so I am not doing the same mistake twice.

Goalie Ca said:

Auto_ptr's have ownership and they are fast

And they have the unfortunate "advantage" of tranferring ownership when they are assigned...which means they can't be used in anything but simple stack jobs. I think the purpose of auto_ptr was to have a scoped ptr, but the semantics are not correct.

HoHo said:

I wonder if I'm missing something but why do people use that much manual memory management?

It depends on the complexity of the program. For small programs, manual memory management is ok. For big complex applications, it's a nightmare. Manual memory management simply does not scale.

HoHo said:

Those programs do not use QT, with QT things would be even simpler even though there would be a lot more manual memory allocations there. QT FTW

Qt is good, but it does not use gc. I am planning to use QObject a lot in the new project (because I need to implement a model-view-controller application), but it's going to be complex, so I am going to hack the Qt sources to use Boehm's gc.

Carrus85 said:

Encourages lazier programming in general(good for quick hack-up programs, bad when the runtime performance of an algorithm is very, very important

Not really. Premature optimization is the root of most evil in quite a few cases. It's easier to identify hot spots using a profiler than trying to manage a memory problem.

Human resources are much more expensive than machines, so if there is a performance problem due to lazy programming, a good option is to throw more hardware at it.

Carrus85 said:

Also encourages "static abuse" in a lot of GC-able languages (for example, concatenating two strings together in Java creates a whole new string, because strings are immutable; major performance bottleneck for those who don't watch their step).

In current Java implementations, string concatenations within an expression is turned to a printf-style expression using StringBuffer.

You have to remember that in Java, allocation is very cheap: the heap pointer is simply incremented. It's like stack allocation.

In c++, although the Boehm's gc is not as fast as Java's (because it's a conservative gc), performance is good (better in some cases) because many objects can be allocated on the stack.

Carrus85 said:

Adds a degree of non-determinism in program runtime

Boehm's gc is incremental (GC_enable_incremental), and it usually does a very good job.

Carrus85 said:

Sometimes you just want control of the memory allocations. (Memory pools are ridiculously fast for allocating a lot of small objects of the same type, for example).

That's why I am not leaving c++ anytime soon.

Carrus85 said:

Doesn't work with RAII (if you must allocate everything on the heap, ala java). And no, finalizers are not a substitute for RAII due to their non-deterministic execution; you know the finalizer will run, but you don't know when it will run, which can be a huge problem if you expect it to run immediately after going out of scope. (Thus, you end up having to finally blocks and other garbage to clean up after yourself, which isn't as clean as RAII.)

I don't know why people keep saying that (I am not saying it for you, as you seem to recognize the advantage of gc in c++). It's not true in c++.

For example, you can allocate your garbage-collected objects on the stack:

File file("temp.txt");

Or you can allocate them on the heap, and use a helper class which does the cleanup. For example:

Temp<File> temp(new File);

In both cases, RAII works beautifully.

It's too bad that other languages don't have stack allocation.

Quote:

Now, that given, the description of Boehm's gc via GC_malloc, it should be possible to just globally override new and delete and use them (assuming GC_malloc doesn't take any other strange arguments or have screwy side effects, as well as a GC_free existing somewhere). This would give you a really interesting benefit, in C++ in particular; you could use RAII where you want, use regular old new for everything else, and get garbage collection for free (in theory). I'd have to look up specifics and run some tests, though.

If you use another library which globally overrides new and delete, then you are out of lack. MFC does that, and it's a pain in the a$$ (and a major source of memory management problems).

One stupid design decision in c++ is that global new and delete operations can not be part of a namespace...it does not allow to put a bunch of classes in a namespace, then declare new and delete once for all of the namespace.

Another stupid decision in c++ is that new and delete operators don't have access to the type of the allocated object, so it's not possible to have a base class with a garbage-collected new and delete that is not polymorphic.

Despite all this, give me c++ with gc anyday over Java and the alternatives. Templates and stack allocation overshadow any disadvantages.

HoHo
Quote:

Qt is good, but it does not use gc

And unless I've completely missed something then it doesn't need to, at least not for anything they themselves provide. Just create the stuff with new and forget about it :)

Quote:

Manual memory management simply does not scale.

I know, that's why I said avoid it completely by using tried and tested containers for objects :P

axilmar
Quote:

And unless I've completely missed something then it doesn't need to, at least not for anything they themselves provide. Just create the stuff with new and forget about it :)

Well, it's not that simple.

For example, take QValidator objects. You insert them into QLineEdit objects like this:

Quote:

QLineEdit *lineEdit = new QLineEdit(bla bla);
lineEdit->setValidator(new QIntValidator(min, max));

The above code just introduced a memory leak!

The correct way to do it is:

Quote:

lineEdit->setValidator(new QIntValidator(min, max, lineEdit));

If you don't pass the owner QObject in the constructor, the validator object will simply never be deleted.

But what if you want to set a new validator to the line edit? You do the following:

Quote:

lineEdit->setValidator(new QDoubleValidator(min, max, lineEdit));

But you introduced another memory leak! the previous validator will be there, waiting to be deleted, but it will never be deleted, and even worse, you can't have a pointer to it (unless you traverse the children objects of line edit and find it and delete it)!

Another example is QPopup and QPushButton:

Quote:

QPushButton *btn = new QPushButton(bla bla);
QPopupMenu *popup = new QPopupMenu();
bla bla fill popup with menu items
btn->setPopup(popup);

The above again introduces a memory leak, because the popup menu will never be deleted.

Say now that you make the popup a child of the dialog it appears into:

Quote:

QPopupMenu *popup = new QPopupMenu(dialog1);

Now the menu will be destroyed when the dialog is destroyed.

Suppose now 3 months pass and another member of your time comes along and decides to reuse the popup menu. He does something like this:

Quote:

QPushButton *btn = new QPushButton("a", dialog2);
btn->setPopup(popupMenu);

Guess what: we don't have a memory leak, but we have a DANGLING POINTER! which is even more dangerous.

Suppose the user opens 'dialog1' and 'dialog2', then closes 'dialog1'. The popup menu will be destroyed, but nothing will happen. The user will not see it if the memory block previously occupied by 'popup' is not reused in anything else. The user might even use the popup menu as it is, even if the popup menu will have been deleted!

Suppose that another object takes over the memory block occupied the popup menu. The object is 'Account', and its vtable has a function 'deleteAccount' at offset N.

As the user uses the popup menu of 'dialog2', an account is deleted! Because the function 'deleteAccount' was invoked instead of 'close()' or something else the menu was supposed to do!

You see, it's not that simple. The only viable solution for big complex projects with lots of developers is garbage collection.

Steve++

I doubt any of you are writing anything where the choice of memory management strategy will make or break your project with respect to performance. One of the biggest reasons I use Java is because it takes care of freeing memory so I don't have to clutter my code with deletes. There are so many more ways in which Java improves developer performance over C++ in leaps and bounds.

I would recommend all serious C++ game programmers take the time to get to know Java. The learning curve is small because the language is simple and tidy, so you can get up to speed in no time. The niche of Java game developers is growing and there are some excellent game libraries such as jMonkeyEngine and LWJGL. And the standard API comes with a ton of stuff; just imagine Allegro as being a tiny subset of it.

As far as performance goes, memory allocation and method calls come very cheap. The garbage collector performs extremely well. The standard API comes with a very fast I/O library (in many cases I/O can bypass the JVM entirely using memory mapping) and if you have a piece of code that really must be optimized, you can write it in C or C++ and use it via JNI.

Carrus85
Steve++ said:

I would recommend all serious C++ game programmers take the time to get to know Java. The learning curve is small because the language is simple and tidy, so you can get up to speed in no time. The niche of Java game developers is growing and there are some excellent game libraries such as jMonkeyEngine and LWJGL. And the standard API comes with a ton of stuff; just imagine Allegro as being a tiny subset of it.

Java is wonderful, with one minor problem; the language is WAY to verbose for my taste. Java follows the "language designer knows best, so lets shackle the programmer" methodology (for example, no operator overloading is allowed. While this is okay, it just adds a lot of clutter when you actually want to overload an operator (not when you are doing funky "lets make + behave like *" crap, but when you are writing, say, a complex number class...) Checked exceptions are another annoying design problem; far to often I see programmers wrapping the checked exception in RuntimeException to get around it. Not that I recommend doing this, it is just way to often I find myself having to just pass on checked exceptions because I cannot do anything about it (and after a while, I get kinda tired of find, for example, that an interface won't let me throw an exception of a particular type, or other garbage)) instead of the "programmer knows best." Look, if I as a programmer actually want to overload an operator, just get out of my way and let me do it. In other words, the signal to noise ratio in a lot of java code I read seems to be annoyingly high for my taste.

Quote:

Quote:
Manual memory management simply does not scale.
I know, that's why I said avoid it completely by using tried and tested containers for objects :P

Agreed. Although, with proper encapsulation, memory management scales much further than trying to manage it all by hand (assuming everybody knows who should own the memory to an item, which can be a large assumption at times...).

Goalie Ca

For a project like firefox, garbage collection is the only solution. There is just way too much going on for any mere mortal to comprehend. In almost all of my projects memory management is not a problem (except when i need to allocate more than 25GB for super large arrays/matrices). Smart pointers are as complicated as i need and even then they are only needed on ocassion. Long-live stack allocation!

A language like java is anti-productive. Extreme verbosity is required without getting anything from it. Exception handling is as big of a mystery as object.kick(ball) or ball.getHit(object).

I'm all for functional programming. Remove all state except where absolutely necessary. Functional languages are short and sweet. Lazy evaluation is even nicer than garbage collection.

Steve++
Quote:

A language like java is anti-productive. Extreme verbosity is required without getting anything from it.

Java is hardly verbose. I'm sure you can show me some contrived examples and call it proof that Java is verbose. In the real world, a lot less code does a lot more work with a lot less bugs when compared to equivalent C++ projects. You have to realise that Java is an improvement on C++, not the other way around. There are very good reasons it excludes a lot of C++ features. And if you really must use operator overloading, mix it with a bit of Groovy, which overloads operators in a much more readable way than C++.

Tobias Dammers
Quote:

Java is wonderful, with one minor problem; the language is WAY to verbose for my taste. Java follows the "language designer knows best, so lets shackle the programmer" methodology (for example, no operator overloading is allowed. While this is okay, it just adds a lot of clutter when you actually want to overload an operator (not when you are doing funky "lets make + behave like *" crap, but when you are writing, say, a complex number class...) Checked exceptions are another annoying design problem; far to often I see programmers wrapping the checked exception in RuntimeException to get around it. Not that I recommend doing this, it is just way to often I find myself having to just pass on checked exceptions because I cannot do anything about it (and after a while, I get kinda tired of find, for example, that an interface won't let me throw an exception of a particular type, or other garbage)) instead of the "programmer knows best." Look, if I as a programmer actually want to overload an operator, just get out of my way and let me do it. In other words, the signal to noise ratio in a lot of java code I read seems to be annoyingly high for my taste.

Very well-worded. I find the exception stuff to be the most annoying feature ever. More often than not, there's nothing you can do about a certain exception, and you just want to pass it through and stop the program.

axilmar
Quote:

ava is wonderful, with one minor problem; the language is WAY to verbose for my taste. Java follows the "language designer knows best, so lets shackle the programmer" methodology (for example, no operator overloading is allowed. While this is okay, it just adds a lot of clutter when you actually want to overload an operator (not when you are doing funky "lets make + behave like *" crap, but when you are writing, say, a complex number class...) Checked exceptions are another annoying design problem; far to often I see programmers wrapping the checked exception in RuntimeException to get around it. Not that I recommend doing this, it is just way to often I find myself having to just pass on checked exceptions because I cannot do anything about it (and after a while, I get kinda tired of find, for example, that an interface won't let me throw an exception of a particular type, or other garbage)) instead of the "programmer knows best." Look, if I as a programmer actually want to overload an operator, just get out of my way and let me do it. In other words, the signal to noise ratio in a lot of java code I read seems to be annoyingly high for my taste.
Very well-worded. I find the exception stuff to be the most annoying feature ever. More often than not, there's nothing you can do about a certain exception, and you just want to pass it through and stop the program.

That's why c++ with garbage collection is better than Java! :-)

EDIT:

According to Hans Boehm, his gc allocates blocks of the same size from the same page, so finding blocks is as simple as dividing the pointer's address (the page offset part, actually) with the block size for that page. It's very very fast, I would dare say faster than manual allocation.

EDIT2:

The D programming language also uses Boehm's gc.

Thread #594447. Printed from Allegro.cc