Re: C++ sucks for games

From: Mark A. Gibbs (x_gibbsmark_at_rogers.com_x)
Date: 10/30/04


Date: Sat, 30 Oct 2004 12:22:58 -0400


Alex Drummond wrote:
> Of course, but GC still improves the situation by reducing the number of
> mistakes it is possible for a programmer to make (although of course memory
> management bugs are still possible in a GC'd language). Just because GC is
> not a pancea doesn't mean it isn't useful.

we could dance in circles on this topic until time ended with no
resolution. my reply to the above would naturally be: just because
manual memory management/smart pointers are not a panacea, that does not
mean they aren't still useful.

i make games, and in the context of games, a gc is kind of a silly idea.
generally speaking, my allocation pattern in games tends along the line
of allocating a large pool at the start of each board/level/or more
recently area, then using smart pointers to lock and unlock sections of
that pool as memory is used, then just freeing the entire pool at the
end. garbage collection adds nothing to this design as it is impossible
for memory to leak (unless the pool doesn't get deallocated, which is
hard to miss).

in fact, the gc would take something away, because non-deterministic
deallocations can fugger things up in exactly the wrong place. i already
evaluated java for game design, and this was one of the major blows
against it - you can't control the gc (portably).

so to avoid another go around, let me repeat what i said at the
beginning. every language has it's strengths and weaknesses. use what
works best for the problem at hand. it is my opinion that c++ is the
best solution for game programming - you can try to convince me
otherwise if you want, but you cannot argue either of the previous
sentences.

>>oh yes, but by following some simple guidelines they can be just as
>>easily avoided. i honestly cannot remember the last time i had a
>>dangling reference crop up in my code.
>
>
> OK, but what if you're using pointers instead of references (which you have
> to some of the time in any real world C++ program). The whole issue of
> references and memory management is a bit of a red herring here. IIRC it
> started from a misunderstanding when I (rather stupidly) used "reference"
> as a general term covering C++ pointers /and/ references. Whatever the
> merrits of C++ references (and IMHO it is quite easy to create dangling
> references as a novice C++ programmer, it took me a while to stop making
> that mistake), you can't do all your memory management using copying and
> (C++) reference passing.

no, you would use smart pointers too. i would give holy hell to anyone
on my design teams that used bare pointers without a very valid reason.

look, advocates of languages that feature garbage collection make a big
deal of "memory management" in c++. the truth is, i rarely think about
it. the topic is fresh on my mind at this precise moment because i just
finished refactoring my game engine's memory management module. after
that's done, i probably won't think about it again for a long time.

i don't spend my programming days sitting there tracking every pointer
and fretting about whether it gets properly deallocated and/or is never
dereferenced when it is not valid. i just write good code, run rigourous
tests then double check with modern tools, which - surprise, surprise -
is the same thing you have to do in any language, gc or no. i haven't
had to track down a memory leak or bad dereference any time in recent
memory.

>>but let's say for argument's sake that you did have to have a shared
>>object and that you could not be sure of it's lifetime.
>>std::tr1::shared_ptr (aka boost::shared_ptr).
>
>
> Yes, but reference counting smart pointers are less efficient than good GC

stop. i have heard that several times in this discussion. i provided
numbers indicating that garbage collection was over 15 times slower than
smart pointers. i have seen absolutely no evidence to the contrary.

this song and dance is not new to me. i heard the same routine in the
early 90's with visual basic, then again later with java. "garbage
collection will free the programmer from the worries of memory
management". "_____ will replace c++ as the linga franca of the
programming world". "c++ is a broken and poorly designed dinosaur, _____
is much better." etc. etc. etc.

call me anal if you want, but i base my judgement on facts. or, in the
absence of reasonably measurable facts, i use current practice as my
metric. if garbage collection is so efficient, then why don't the
industry sectors that make their livelihood trying to make the most
efficient programs possible use gc capable languages? go ahead and say
"because they're all old fogies set in their way and buying into old
lies and afraid to change for the future", it will only prompt me to ask
why someone hasn't come along to upset the status quo and force them to
catch up.

ordinarily i'd say bring numbers and i'll consider your point. however,
i *have* numbers, numbers indicating that garbage collection is slower
than smart pointers. so your job now is not only to bring your own
numbers, but to give me an acceptible reason for why my numbers are
wrong. show your work.

as i said before, as far as i'm concerned, all pointers in a garbage
collected environment are smart pointers. you can all take turns calling
me ignorant about lisp or generational garbage collection again if you
like, but i just don't see how gc can be implemented otherwise. they may
not be reference counted, they may not even be implemented by linked
list, but *somehow* *something* has to be aware of when the allocated
memory is no longer being referenced. so *somehow* the references in the
program are being tracked. do i care how? no, not really. it doesn't
change the fact that they are conceptually smart pointers.

> of your program) and you have to use the clumsy template syntax in C++ (if
> only it had type inference...)

template <typename T>
inline std::tr1::shared_ptr<T> make_shared_ptr(T*) {
    return std::tr1::shared_ptr<T>(p);
}

// in use:
std::tr1::shared_ptr<foo> p_foo = make_shared_ptr(new foo);
std::tr1::shared_ptr<bar> p_bar = make_shared_ptr(new bar);

>>but c++ is more suited to a more deterministic
>>problem domain. vaguely allocating and tossing memory around with no
>>concept of ownership or responsibility isn't a good idea in tighter
>>architectures, or even in more expansive architectures where you want
>>close control of resources. if memory is cheap and plentiful, then sure,
>>but then c++ may not be the best tool for the job.
>
>
> I realise I'm making a little more or your comment here than is strictly
> speaking fair, but given that memory /is/ cheap and plentiful on modern
> computers, would that not mean that C++ is not usually the best tool for
> the job for modern application design?

that would depend on several things, not the least of which is speed,
just how much of that plentiful memory you have to use and whether or
not you want to flip the proverbial bird to a chunk of your potential
audience because you couldn't be bothered to use their limited memory
more efficiently.

making word perfect? then you have a strong case for why c++ may not be
the best tool.

making doom 3? i dare you to compete against me in the open market with
a game engine that is slower and requires more memory than mine.

> As for "vaguely allocating and tossing memory around with no concept of
> ownership and responsibility", this is sometimes necessary.

it is never necessary. however, you may make a design decision to do
this in order to simplify the design.

on the other hand, it *is* sometimes necessary to keep a tight rein on
how much, how, and when memory is allocated and freed.

> The concept of
> a particular module/class/bit of a program "owning" a region of memory is
> not always a particularly natural one. C++ forces you to use this concept
> to make memory management manageable, but sometimes it doesn't make much
> sense to treat memory as a "resource" managed by a particular part of the
> program. This is when you need to start using smart pointers

it is fair to say that c++ *recommends* that you treat memory as a
finite system resource that must be acquired before use and released
when no longer needed. (forced? no. you are free to do what you want -
including using garbage collection - in c++.) but, and forgive me for
pointing out the obvious, isn't that what memory *is*?

as for whether or not the model of a single module handling memory
management is natural or not, that's a matter of opinion. however, in
the most common case, doesn't memory get allocated and freed from a
single place (the operating system's allocation and deallocation
functions)? regardless, you're free to do all your memory management in
one place or not. it's your choice.

> (/smart/
> pointers: auto_ptr is helpful, but the memory management problem it solves
> is not exactly thorny -- just add a few delete statements at the end of
> your function).

whoa whoa whoa. i may be ignorant of the design of modern garbage
collected environments, but it's clear that you are similarily ignorant
of how to manually manage memory in modern c++.

you don't just go sticking delete's everywhere then pat yourself on the
back for a job well done. do that and i can damn well guarantee that you
will end up with dangling pointers and/or double delete's.

if i ran a search on my entire game engine code, i would expect to find
only a dozen delete's in several thousand lines (including comments i
guess) - and all of them in the memory management module. i intend to
track any others down myself and fix them.

i paid several hundred dollars for a nice professional c++ development
environment, and i'll be damned if i'm going to manually delete every
pointer when the compiler is perfectly well capable of doing it for me.

besides - just where exactly is the "end" of any given function. i can
tell you now that *that* is most assuredly a thorny issue, in just about
any langauge, but most especially modern ones.

> This isn't just Lisp propaganda. For example take a look at the rationale
> for Boehm GC at http://www.hpl.hp.com/personal/Hans_Boehm/gc/issues.html.
> One quote:
>
> "In general, it is considerably easier to implement and use sophisticated
> data structures in the presence of a garbage collector. As a result, it is
> easier to avoid imposing arbitrary limits on program parameters (e.g.
> string lengths), and to ensure that performance scales reasonably for large
> inputs. ****** The cord string package included with our garbage collector
> is an example of a useful abstraction that could not be provided without
> some form of garbage collection, and would be more expensive with
> user-provided reference counting, or the like ******. A program based on
> something like the cord package has some chance of continuing to run
> reasonably if it receives a 10 megabyte input string where the programmer
> expected a line of text. This is much less likely with a string package
> that uses a conventional representation."
>
> (****** emphasis is mine, of course)

again, forgive me for pointing out the obvious, but if the programmer
was expecting to get a single line of text as input, wouldn't a 10MB
chunk of data be an error? or, alternately, if the program is supposed
to be able to handle a 10MB input string, wouldn't the programmer be in
error for not expecting it?

besides:

#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>

void make_file();
std::string read_file();

int main() {
    std::cout << "Making file...." << std::endl;
    make_file();

    std::cout << "Reading string...." << std::endl;
    std::string input = read_file();

    std::cout << "Input size = " << input.length() << std::endl;

    std::system("pause");
    return 0;
}

void make_file() {
    std::ofstream out("file.txt");

    for (int i = 0; i < 10485760; ++i)
       out << 'a';
}

std::string read_file() {
    std::ifstream in("file.txt");

    std::string file_contents;
    in >> file_contents;

    return file_contents;
}

output:
Making file....
Reading string....
Input size = 10485760
Press any key to continue . . .

plus a text file with over 10 million letter "a"'s. i don't exactly know
what the boehm people consider a "conventional representation", but this
string wraps a plain old c-style array.

where's the "memory management"? i admit this is a trivial program, but
it does create a 10MB file using buffered writes, then input a 10MB file
- again, using buffered reads - into a 10MB long string (and it may or
may not copy that string, we can't be sure without more knowledge of the
specific string implementation and compiler), and there's not a single
iota of any conscious memory management to be seen.

indi



Relevant Pages

  • Re: C++ Garbage Collector on VMS?
    ... worry about memory management in a virtual memory OS design. ... The Language and the Language Run-Time is generally left holding the proverbial bag, if the bag isn't passed directly to the programmer. ...
    (comp.os.vms)
  • Re: ASTs in assembler
    ... Absolutely I don't mean optimizing what has written by the programmer. ... idea of the assembler being able to have a "big picture" of the source. ... Because of the amount of memory modern machines have, ... Why not read macro bodies from macro symbol table? ...
    (alt.lang.asm)
  • Re: what is the purpose of C++ smart pointer
    ... You confuse reference counting with smart pointers. ... following the changes of the memory it points to. ... int * x = new int; ...
    (comp.os.linux.development.apps)
  • Re: C++ sucks for games
    ... > language, including plain english. ... > dangling reference crop up in my code. ... references and memory management is a bit of a red herring here. ...
    (comp.lang.lisp)
  • Re: Memory Management scares me
    ... I kind of regret not having gone into programming at that ... It wasn't so much the idea of memory leaks as the idea of ... > are the driving factors for selecting customized memory management. ... simply replicates the entire data structure on each iteration, ...
    (comp.lang.cpp)