Live data from Hacker News

Why I Write Games in C (yes, C)

jonathanwhiting.com

161–170 of 556 posts

Re: Why I Write Games in C (yes, C)

#161
post #46

> The stop-the-world garbage collection is a big pain for games, stopping the world is something you can't really afford to do. I love this opinion from games programmers because they never qualify it and talk about what their latency budgets are and what they do in lieu of a garbage collector. They just hand wave and say "GC can't work". The reality is you still have to free resources, so it's not like the garbage c…

I’m not a game programmer but with RAII for example (I realize this is not a C idiom), it really becomes a non-issue if you have things scoped properly. For everything that doesn’t fit into this box you probably wouldn’t be relying on a GC anyway, the GC may do the final memory reap but scope control is still often manual in memory-managed languages.

That's not entirely true. If you only rely on RAII you may end up with millions of shared pointers, each of which needs to be managed independently and having its own overhead. They may also be independently allocated, which means they will be fragmented in memory. Not so simple.

Re: Why I Write Games in C (yes, C)

#162
post #132

Earlier quoted context omitted.

Disclaimer: I'm not a game developer; but, I've worked on a lot of projects with tight frame time requirements in my time at Netflix on the TVUI team. I also have no experience in Go so I can't comment on the specifics of that garbage collector vs. V8. I don't think it's necessarily that it "can't" work as much as it takes away a critical element of control from the game developers and the times you find yourself "at…

With JS the trick is to avoid creating new objects and instead have a pool of objects that are always referenced.

Definitely! Object pools are common in game dev too from what I know. We used them extensively to reduce the amount of GC we needed.

Re: Why I Write Games in C (yes, C)

#164

I get that this is not a popular opinion these days, but I like C. Quite a bit, actually. I can get stuff done reasonably quickly, don't have to futz around with multiple inheritance (C++) or where to call out to C anyway (python). There is so much more tooling behind C than Rust, or Go, or Zig (though I've been looking at Zig as a replacement). C isn't as bad as everyone makes it out to be, for personal stuff. I'd b…

I like C as well. I’ve been working it for a long-ass time, enough to be comfortable with it. Two immediate things I would love would be overloading (but maybe not) and something lighter compared to ICU4C when dealing with text processing. There are nice things like SDS, but not even close to what ICU does. It might not even be possible to do everything ICU does in less weight anyways.

Re: Why I Write Games in C (yes, C)

#165

> [C++] is high performance, and it offers but features I don't want, and at a great complexity cost. 1. Complexity of implementation is not complexity of use. Example: Take std::array vs a plain C array. std::array is a few hundreds of lines of code. But - it's about as inutitive to use; has more convenience features; easier for the compiler to optimize; and doesn't let you should yourself in the foot that easily. 2…

Some people just like writing code. You can't say that C's features are insufficient, when you can implement the vast majority of C++ features in native C, and all of 'em with tooling. What's wrong with having direct control of only the features you need? What's wrong with code generation? Not to trample on C++, I like it (albeit less than C). I would definitely create C++ if it didn't exist.

Re: Why I Write Games in C (yes, C)

#166
post #46

> The stop-the-world garbage collection is a big pain for games, stopping the world is something you can't really afford to do. I love this opinion from games programmers because they never qualify it and talk about what their latency budgets are and what they do in lieu of a garbage collector. They just hand wave and say "GC can't work". The reality is you still have to free resources, so it's not like the garbage c…

I used to work on realtime graphics code which was used in flight simulators for the FAA and USAF, then moved into the games industry and led the design of a couple of game engines, to give you an idea where I'm coming from. The military grade flight sims actually had contract requirements like, "you may never miss a single frame in a week of usage", etc, so solving this problem was critical.

When working on code such as game engines, it's not simply a matter of how long something takes to do, but also when it happens and how predictable it is. If I knew that GC takes 1ms, I could schedule it just after I call swapBuffers and before I begin processing data for the next frame, which isn't a latency critical portion.

The problem is that GC is unpredictable because the mark and sweep can take a long time, and this will prevent you from meeting your 60fps requirement.

In practice, we hardly ever do any kind of GC for games because we don't really need to. We use block memory allocators like Doug Lea's malloc (dlmalloc) in fixed memory pools if we must, but generally, you keep separate regions for things like object descriptors, textures, vertex arrays, etc. There's a ton of data to manage which can't be interleaved together in a generic allocator, so once you've gone that deep, there's really no point in using system malloc.

Malloc itself isn't a problem either, it's quick. It's adding virtual space via sbrk() and friends which can pause you, so we don't. On consoles, we have a fixed memory footprint, and on a bigger system, we pre-allocate buffers which we will never grow, and that's our entire footprint. We then chunk it up into sections for different purposes, short lived objects, long lived objects, etc. Frequently, we never even deallocate objects one by one. A common tactic is to have a per-frame scratch buffer, and once you're done rendering a frame, you simply consider that memory free - boom, you've just freed thousands of things without doing anything.

There are many things you can do instead of generic GC which are far better for games.

I disagree with the author of the original article about C++. C++ is as complex as you want to make it, you don't have to use all the rope it hands you to hang yourself. However, having the std library with data structures, smart pointers, the ability to do RAII, is invaluable to me. Smart usage of std::ref gets you automatic GC, what you don't have is cycle detection, so you take care never to have cycles by using weak pointers where necessary, and you have all the behavior of auto-GC without stop the world.

Re: Why I Write Games in C (yes, C)

#167
post #151
post #98

Earlier quoted context omitted.

as an example point, the Go garbage collector clears heaps of 18gb in sub-millisecond latencies. If I'm understanding the problem at hand (maybe I'm not!), given an engine running at a target framerate of 144 frames per second, you're working with a latency budget of about 7ms per frame. Do you always use all 7ms, or do you sometimes sleep or spin until the next frame to await user input? We can also look at it from…

"Clearing an 18GB heap" that's full of 100MB objects that are 99% dead is different than clearing an 18GB heap of 1KB objects that are 30% (not co-allocated, but randomly distributed across the whole heap).

this is exactly the kind of dismissive hand-waving that is frustrating. The 18gb heap example is from data collected on production servers at Twitter. Go servers routinely juggle tens of thousands of connections, tens of thousands of simultaneous user sessions, or hundreds of thousands of concurrently running call stacks. We're essentially never talking about 100mb objects since the vast majority of what Go applications are doing is handling huge numbers of small, short-lived requests.

https://blog.golang.org/ismmkeynote

Re: Why I Write Games in C (yes, C)

#168

Earlier quoted context omitted.

I don't do much in C anymore and definitely never used a library that had a typedef'd pointer type. If I had to guess I'd imagine it's something like: variables that are pointers are syntactically different. Unless they're used "opaquly" I guess. If I see a declaration like "Foobar baz;" I don't know which of "baz.boo", "baz->boo" or "baz[x]" are syntactically valid at a glance. Dunno if that's a huge deal but it's a…

The thing is, even with the raw pointer the hard part is resolving ownership questions, not resolving the type. The tricky questions in C are: am I responsible for freeing it or is the library? If I send it into this function call am I transferring ownership? Type deffing doesn't make any of that easier or harder to figure out. Whether the thing is a handle or a pointer doesn't really complicate things, if that makes…

I get what you're saying, but I'm speculating what not type defing might help with. It's clearly advice that drifts around. Even if you think it doesn't help much it must address something for somebody, right?

Re: Why I Write Games in C (yes, C)

#169

Makes some really great points, similar to why C++ is not favored for embedded. C++ compilers are just too unpredictable and when trying to meet code limits in 128K, 32K or even 2K of flash, C++ is a nightmare. All of the major embedded SDKs (STM, Microchip, SiLabs, TI, NXP, I could go on) are written in C... and C only. MBED is C++, but I've never encountered or heard of an OEM or integrator using MBED in a real pro…

Lots of folks use C++ on microcontrollers (including me!). I've definitely been working closer to the 32 bit processor, >= 128K flash world though.

Certain things are very nice about C++ as compared to C, especially C++11 on:

* type safety and expressiveness (aided by judicious use of templates)

* The constexpr keyword for real compile-time constants (again, type safety)

* RAII (often aided by statically allocated pools of memory from which one can then allocate objects from at runtime)

What kind of issues with code size have you had (and using which compiler)?

Re: Why I Write Games in C (yes, C)

#170
post #132

Earlier quoted context omitted.

Disclaimer: I'm not a game developer; but, I've worked on a lot of projects with tight frame time requirements in my time at Netflix on the TVUI team. I also have no experience in Go so I can't comment on the specifics of that garbage collector vs. V8. I don't think it's necessarily that it "can't" work as much as it takes away a critical element of control from the game developers and the times you find yourself "at…

With JS the trick is to avoid creating new objects and instead have a pool of objects that are always referenced.

Once I wrote a very small vector library in JS for this very reason: almost all JS vector libraries out there tend to dynamically create new vectors for every vector binary operation, this makes JS GC go nuts. It's also prohibitively expensive to dynamically instantiate typedarray based vectors on the fly, even though they are generally faster to operate on... most people focus on fixing the latter in order to be able to use typedarrays by creating vector pools (often as part of the same library), but this creates a not-insignificant overhead.

Instead my miniature library obviated pools by simply having binary operators operate directly on one of the vector objects passed to it, if more than one vector was required for the operation internally they would be "statically allocated" by defining them in the function definition's context (some variants i would also return one of these internal vectors - which was only safe to use until a subsequent call of the same operator!).

The result this had on the calling code looked quite out of place for JS, because you would effectively end up doing a bunch of static memory allocation by assigning a bunch of persistent vectors for each function in it's definition context, and then you would often need to explicitly reinitialize the vectors if they were expected to be zero.

... it was however super fast and always smooth - I wish it was possible to turn the GC off in cases like this when you know it's not necessary. It was more of a toy as a library, but i did write some small production simulations with it - i'm not sure how well the method would extend to comprehensive vector and matrix libraries, I think the main problem is that most users would not be willing to use a vector library this way, because they want to focus on the math and not have to think about memory.

Post reply on HN