Live data from Hacker News

Why I Write Games in C (yes, C)

jonathanwhiting.com

281–290 of 556 posts

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

#281
post #267

Earlier quoted context omitted.

I just want to say that no, you don’t need to free the resources. It's very possible to use a fix amount of heap memory during the whole lifecycle of your game. In fact I think that’s what people should aim for in 90% of the cases.

You can do the same thing in GC'd languages, though.

Here's the easy question: can you do manual GC in a GC'd environment? Yes, of course you can.

Here's the harder question: how hard is it to do manual GC in a GC'd environment vs. a non-GC'd environment?

"Environment" is the key word here. Because if you're writing in a GC'd environment, there's a good chance that existing code - third party, first party, wherever it comes from - assumes that it can heap allocate objects and discard them without too much thought.

So for small scale optimizations where you own it all, it can work out fine. But if that optimization needs to bust through an abstraction layer, all of a sudden the accounting structure of the whole program has changed, and the optimization has turned into a major surgical operation.

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

#282
post #170
post #132

Earlier quoted context omitted.

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

You wouldn't have this still up somewhere like GH would you? I'm currently writing a toy ECS implementation and have somewhat similar needs, and I've been trying to build up a reference library of sorts covering novel ways of dealing with these kind of JS issues

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

#283

Earlier quoted context omitted.

Which is why you generally almost never use the standard malloc to do your piecemeal allocations. A fair number of codebases I've seen allocate their big memory pools at startup, and then have custom allocators which provide memory for (often little-'o') objects out of that pool. You really aren't continually asking the OS for memory on the heap. In fact, doing that is often a really bad idea in general because of th…

Doesn’t this just change semantics? Whatever custom handlers you wrote for manipulating that big chunk of memory are now the garbage collector. You’re just asking for finer grained control than what the native garbage collection implementation supports, but you are not omitting garbage collection. Ostensibly you could do the exact same thing in e.g. Python if you wanted, by disabling with the gc module and just writi…

C's free() gives memory back to the operating system(1), whereas, as a performance optimization, many GCd languages don't give memory back after they run a garbage collection (see https://stackoverflow.com/questions/324499/java-still-uses-s...). Every Python program is using a "custom allocator," only it is built in to the Python runtime. You may argue that this is a dishonest use of the term custom allocator, but custom is difficult to define (It could be defined as any allocator used in only one project, but that definition has multiple problems). The way I see it, there are allocators that free to the OS and those that don't or usually don't (hereafter referred to as custom). In C, a custom allocator conceivably could be built into, say, a game engine. You might call ge_free(ptr) which would signal to the custom allocator that chunk of memory is available and ge_malloc() would use the first biggest chunk of internally allocated memory, calling normal malloc() if necessary. Custom allocators in C are a bit more than just semantics, and affect performance (for allocation-heavy code). Furthermore, they are distinct from GC, as they can work with allocate/free semantics, rather than allocate/forget (standard GC) semantics. Yes, one could technically change any GCd language to use a custom allocator written by one's self. But Python can't use allocate/free semantics (so don't expect any speedup). Python code never attempts manual memory management, (i.e. 3rd party functions allocate on the heap all the time without calling free()) because that is how Python is supposed to work. To use manual memory management semantics in Python, you would need to rewrite every Python method with a string or any user defined type in it to properly free.

(1) malloc implementations generally allocate a page at a time and give the page back to the OS when all objects in the page are gone. ptr = malloc(1); malloc(1); free(ptr); doesn't give the single allocated page back to the OS.

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

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

>> The reality is you still have to free resources,

Not exactly. Here is how the early PC 3D games I worked on did that: They would have a fixed size data buffer initialized for each particular thing you needed a lot of, such as physics info, polygons, path data, in sort of a ring buffer. A game object would have a pointer to each segment of that data it used. If you removed a game object you would mark the segment the game object pointed to as unused. When a new object was created you would just have a manager that would return a pointer to a segment from the buffer that was dirty that the new object would overwrite with data. Memory was initialized at load and remained constant.

One problem with doing things like that is that you would have fixed pool. So there were like 256 possible projectiles in Battlezone(1998) in the world at any time and if something fired 257th an old one just ceased to exist. Particles systems worked that way as well.

What was good about that was that you could perform certain calculations relatively fast because all the data was the same size and inline, so it was easy to optimize. I worked on a recent game in C# and the path finding was actually kind of slow even though the processor the game ran on was probably like 100 times (or more) faster. I understand there are ways to get C# code to create and search through a big data structure as fast as the programmers had to do it in C in the 90's. However it would probably involve creating your own structures rather than using standard libraries, so no one did it like that.

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

#285
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 worked as a games programmer for 8 years in C/C++, and spent an accumulated 2 years just doing optimisation, during the time of 6th and 7th generation consoles. Freeing resources in a deterministic manner is important for the following reasons:

FRAME RATE: having a GC collect at random frames makes for jerky rendering

SPEED: Object pools allow reuse of objects without allocing/deallocing, and can be a cache-aligned array-of-structs. Structs-of-arrays can be used for batch processing large volumes of primitives. https://en.wikipedia.org/wiki/AoS_and_SoA

RELIABILITY: This is probably applicable to the embedded realm too, but if you can't rely on virtual memory (because the console's OS/CPU doesn't support it, or once again you don't want the speed impact) then you need to be sure that allocations always succeed from a fixed pool of memory. Pre-allocated object pools, memory arenas, ring buffers etc. are a few of ways to ensure this.

There's probably a lot more, but those are the reasons that jump out at me.

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

#286

Earlier quoted context omitted.

+1. We have a large Rust code base, and we forbid Vec and the other collections. Instead, we have different types of global arenas, bump allocators, etc. that you can use. These all pre-allocate memory once at start up, and... that's it. When you have well defined allocation patterns, allocating a new "object" is just a "last += 1;` and once you are done you deallocate thousands of objects by just doing `last -= size…

Do you use any public available crate for those allocators? Would love to take a look. I'm currently trying to write a library for no-std which requires something like that. I currently have written a pool of bump allocators. For each transaction you grab an allocator from the pool, allocate as many objects from it as necessary, and then everything gets freed back to the pool. However it's a bit hacky right now, so I…

Seems like lifeguard could solve this: https://github.com/zslayton/lifeguard

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

#287

Earlier quoted context omitted.

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.

> when you can implement the vast majority of C++ features in native C No, you absolutely cannot, even in principle. C++ is not "C with classes" and some syntactic sugar like it started out. That's not been the case for many years already. Also, even the features you can implement - you won't; you don't have the person-years for that. You will have to, need to, use libraries. For those you need to compare the librari…

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.

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

#288
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 worked as a games programmer for 8 years in C/C++, and spent an accumulated 2 years just doing optimisation, during the time of 6th and 7th generation consoles. Freeing resources in a deterministic manner is important for the following reasons: FRAME RATE: having a GC collect at random frames makes for jerky rendering SPEED: Object pools allow reuse of objects without allocing/deallocing, and can be a cache-aligned…

you can turn off the gc in Go and run it manually. you can also write cache-aligned arrays of structs in Go if you want to. you can allocate a slab and pull from it if you want to. the existence of a GC doesn't preclude these possibilities.

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

#289
post #288

Earlier quoted context omitted.

I worked as a games programmer for 8 years in C/C++, and spent an accumulated 2 years just doing optimisation, during the time of 6th and 7th generation consoles. Freeing resources in a deterministic manner is important for the following reasons: FRAME RATE: having a GC collect at random frames makes for jerky rendering SPEED: Object pools allow reuse of objects without allocing/deallocing, and can be a cache-aligned…

you can turn off the gc in Go and run it manually. you can also write cache-aligned arrays of structs in Go if you want to. you can allocate a slab and pull from it if you want to. the existence of a GC doesn't preclude these possibilities.

Why pick a language that has a feature you need to immediately turn off? Some people probably want to, but ... why?

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

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

> The reality is you still have to free resources, so it's not like the garbage collector is doing work that doesn't need to be done.

The garbage collector also needs to track resources, which is an additional cost over just freeing them. You have little control over how the memory is allocated, which is an additional cost over a design that intelligently uses different allocation strategies for different types of resources. Then, even if you can control when the garbage collector is invoked, you have little control over what actually gets freed. What good are those 200µs if the stuff eating up memory isn't actually getting freed fast enough?

Maybe people often overestimate their performance needs. A garbage collector may be fast enough for more purposes than anticipated. Even then, managing memory intelligently may seem like a small price to pay compared to the prospect of eventually fighting a garbage collector to get out of a memory bottleneck.

Post reply on HN