Live data from Hacker News

Why I Write Games in C (yes, C)

jonathanwhiting.com

361–370 of 556 posts

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

#361

> [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…

Also, the C++ committee is very serious about not paying for what you don't use. You can rest assured that there's no unused, hidden C++ bloat that is chipping away at your frame budget.

This is not meant to be a strawman against the complexity cost argument, it's just a related feature of C++ that I thought might be worth explicitly pointing out in case anyone is misinterpreting.

Also, if anyone has a credible dispute against the claim I'd like to hear it.

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

#362
post #318
post #278

Earlier quoted context omitted.

I have actually worked on a system where malloc() was forbidden. In fact it always returned null. Buffers were all statically allocated and stack usage was kept to a minimum (it was only a few kB anyways). The software was shipped with a memory map file so you know exactly what each memory address is used for. A lot of test procedures involved reading and writing at specific memory locations. It was for avionics BTW.…

Why even define a malloc() in this environment if it always returns NULL?

Some things are okay to allocate at some point in time. So you can disable malloc later when you no longer want it to provide memory.

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

#363
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…

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…

There's probably 500+ successful GC based games on the Steam store another 100000 to a million hobby games doing just fine with GC.

I started game programming on the Atari 800, Apple 2, TRS-80. Wrote NES games with 2k of ram. I wrote games in C throughout the 90s including games on 3DO and PS1 and at the arcade.

I was a GC hater forever and I'm not saying you can ignore it but the fact that Unity runs in C# with GC and that so many quality and popular shipping games exist using it is really proof that GC is not the demon that people make it out to be

Some games made with GC include Cuphead, Kerbal Space Program, Just Shapes & Beats, Subnautica, Ghost of a Tale, Beat Saber, Hollow Knight, Cities: Skylines, Broforce, Ori and the Blind Forest

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

#364

Earlier quoted context omitted.

So which feature can you not implement? The only I can think of which you'd have real problems are anonymous functions/lambdas. Most other features I think you can implement. It won't look the same, but it will serve the same purpose.

Things coming to mind: Pointers which can't be NULL (aka. references) Exceptions which also clean up intermediate frames (the exception itself can be done with longjmp, but no destructors which could be run)

Pointers that can't be null are usually implemented by struct embedding. That's typical for Linux kernel code, for example.

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

#365

Earlier quoted context omitted.

Wow - disabling the caches is an extreme measure. I get it though. After doing that, you can (probably) do cycle counting on routines again. Just like the 80s or earlier.

I don't. Maybe we're thinking about different kind of caches, but if these are transparent, no-performance-impact caches, then why wouldn't you prove the system works well with caches off (guarantee deadlines are met), then enable caches for opportunistic power gains?

> if these are transparent, no-performance-impact caches

If there were no performance impact, there would be no point. I'm not just being snarky; there's an important point here. Caches exist to have a performance impact. In many domains it's OK to think about caching as a normal case, and to consider cache hit ratio during designs. When you say "no performance impact" you mean no negative performance impact, and that might be technically true (or it might not), but...

But that's not how a hard real-time system is designed. In that world, uncached has to be treated as the normal case. Zero cache hit ratio, all the time. That's what you have to design against, even counting cycles and so on if you need to. If you're designing and testing a system to do X work in Y time every time under worst-case assumptions, then any positive impact of caches doesn't buy you anything. Does completing a task before deadline because of caching allow you to do more work? No, because it's generally considered preferable to keep those idle cycles as a buffer against unforeseen conditions (or future system changes) than try to use them. Anything optional should have been passed off to the non-realtime parts of the larger system anyway. There should be nothing to fill that space. If that means the system was overbuilt, so be it.

The only thing caches can do in such a system is mask the rare cases where a code path really is over budget, so it slips through testing and then misses a deadline in a live system where the data-dependent cache behavior is less favorable. Oops. That's a good way for your product and your company to lose trust in that market. Once you're designing for predictability in the worst case, it's actually safer for the worst case to be the every-time case.

It's really different than how most people in other domains think about performance, I know, but within context it actually makes a lot of sense. I for one am glad that computing's not all the same, that there are little pockets of exotic or arcane practice like this, and kudos to all those brave enough to work in them.

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

#366
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…

> latency budgets

The problem with gc is that it can blow out a frame's budget, and perhaps extend for more than one frame, taking away precious nanosecs, dropping a 60fps down to 15.

> what they do in lieu of a garbage collector.

Use reference counting. If in the rare case you need circular references then you use a weak pointer, or reclassify your structure so that it's not circular.

That said, where and how you manage the heap can be critical. I recall a few places where I've used the stack itself as a temporary (small) heap, which all gets cleaned up when you exit the function, taking up almost zero clocks to do so.

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

#367
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…

You don't garbage collect. Aaah game I worked on had p much every object type pre allocated at boot. Things ran until you hit a loading point. At which point you wrote new object data over the existing objects. This isn't garbage collection technically but it's the same thing. Ever wonder why some games have shit load times? Cause that's when the equivalent of gc is happening. Except like everything else in games it's coded by the engine team instead of the language implementer and so is hooked into and looks different. The game knows when it can go to shit and all of the optimizations have been done pre launch usually (if it's performant).

Basically think about how you can either build a function as recursive and leverage the stack that is built into the programming language or you can write the exact same algorithm in a for loop with a stack object and manipulate the flow of the recursion yourself. they're essentially the exact same thing however one is in complete control of the application writer and the other one is in control of the language. if you use the for lube you can actually say I'm going to run out of memory before you hit the stack limit and actually do something smart it's much harder to do this in a programming language with a built-in recursion. for instance you might approach the stacked limits in the for loop and just returned the current answer.

essentially this analogy goes for the difference between garbage collection in many games and garbage collection in garbage collected languages

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

#368
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…

Even Unreal Engine has garbage collection for UObjects. I love C but I don't think it's necessarily the best language for games programming anymore, I believe C++ is more suited since games fit into the OO paradigm so much better. I am only a hobbyist though so my opinion probably doesn't matter. EDIT: Unreal Engine is primarily written in C++, and has garbage collection. I'm not salty about the downvote, but I would…

You're not wrong about GC becoming more common in games, as you say Unreal has a GC and so does Unity.

Unreal Engine's GC at least is pretty custom. A lot of effort has been put into it to allow developers to control he worst case run time. It has a system that allows incremental sweeps (and multi-threaded parallel sweeping) and will pause its sweeps as it approaches its allotted time limits.

That said, in my experience with Unreal most large projects choose to turn off the per-frame GC checks and manually control it. For example one project I've seen only invoked the GC when the player dies, a level transition happens or once every 10 minutes (as a fallback).

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

#369
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…

> If you have a garbage collector that runs in 200us The problem is GCs for popular languages are nowhere near this good. People will claim their GC runs in 200us, but it's misleading. For example, they'll say they have a 200us "stop the world" time, but then individual threads can still be blocked for 10ms+. Or they'll quote an average or median GC time, when what matters is the 99.9th percentile time. If you run GC…

It's also important to consider how often the GC runs. GC that runs in 200us but does so ten times within a frame deadline might as well be a GC that runs in 2ms. Then there are issues of contention, cache thrashing, GC-associated data structure overhead, etc. The impact of GC is a lot more than how long one pass takes, and focusing on that ignores many other reasons why many kinds of systems might avoid GC.

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

#370

Earlier quoted context omitted.

I know this subject quite well and I will later publish a detailed article. The real run-time cost of memory management done well in a modern game engine written without OOP features is extremely low. We usually use a few very simple specialized memory allocators, you'd probably be surprised by how simple memory management can be. The trick is to not use the same allocator when the lifetime is different. Some resourc…

It would be interesting to see what the impact of adding hints about allocation lifetime to malloc would be. I have to suspect someone has already written that paper and I just don't know the terminology to look for it.

I'm reminded of an ancient 8031 C compiler. It didn't support reentrancy. Which seems like a bad thing until you realize that the program could be analyzed as a acyclic graph. The compiler then statically allocated and packed variables based on the call tree. Programs used tiny amounts of memory.
Post reply on HN