Live data from Hacker News

Goodbye C++, Hello C

momentsingraphics.de

81–90 of 222 posts

Re: Goodbye C++, Hello C

#81
post #76
post #49

Earlier quoted context omitted.

> the general trend of nudging, cajoling more and more coders into using managed, very high level, safe languages and runtimes This is a good thing: these languages and runtimes are indeed much safer, and also much more productive than C. You can even still get the same amount of low-level control with Rust. > in general discouraging peeking under the hood, at the hardware level, as something raw, wild or unsafe. C i…

This is a good thing: these languages and runtimes are indeed much safer, and also much more productive than C. You can even still get the same amount of low-level control with Rust. How do you bootstrap languages like Rust? Another 'safe' language? What about that one? Someone somewhere has to be working at the asm level.

Bootstrapping and language safety are orthogonal. C is unsafe and still you can't bootstrap it if you don't already have a compiler which can compile your C compiler. According to that logic even assembly is not low level enough because you need an assembler to make a runnable program out of it.

Re: Goodbye C++, Hello C

#82
post #74

Earlier quoted context omitted.

Why would a renderer need to deallocate memory? And when would a renderer need to deallocate memory?

Renderers need to deallocate memory all the time. For example, they often need to store per-frame data that gets dropped when the frame is over, debug strings, track temporary access to texture/buffer data, manage command buffers, etc. They also have to track resources for lots of data types external to the program (such as GPU data) which often forces them to work with external allocators, as well as having to synch…

> For example, they often need to store per-frame data that gets dropped when the frame is over, debug strings, track temporary access to texture/buffer data, command buffers, etc.

Exactly. You can just bulk allocate all the memory for the frame at the beginning and drop the entire thing after the frame is finished. This is a very easy case for manual memory management where you have one allocation and one deallocation per frame.

Or you can do one better, and keep an arena for each framebuffer, and then just recycle them and never deallocate at all. If you really need some level of dynamism in terms of memory usage, you can just double the size of the arena every time you fill it, and you still don't need to deallocate, since chances are you will need that space again for some frame in the future.

Re: Goodbye C++, Hello C

#83
post #56

Earlier quoted context omitted.

The problem is not even just safety. You can write safe C, if you are careful (and you need to be careful also for C++, smart pointers do not just magically make everything right). The point is that OP makes the point that C requires to write less code, and this doesn't even seem true: you have to remember, each time some non-trivial object goes out of scope, to call its destructor/deallocator, which results in a lot…

> You can write safe C, if you are careful You can also live forever, if you have right combination of genetics and environment.

You analogy makes no sense. By your measure, nothing is safe.

Re: Goodbye C++, Hello C

#84

> Where C++ gives you many slightly different takes on the same concept (e.g. unique_ptr, shared_ptr and raw pointers), C gives you one way to go and that one has a conveniently compact notation (such as float ). You use unique_ptr and shared_ptr because float is unsafe. If you don't care about the safety that smart pointers provide, you can use float* in C++ too. > ticking with the example of matrix math, it is baff…

"safety" is really overrated. The paranoia-fueled security industry has turned programming into some sort of weird authoritarian dystopia.

You've done it now. Get ready for the vengeance of the RDF (Rust Defence Force).

Re: Goodbye C++, Hello C

#85
post #82

Earlier quoted context omitted.

Renderers need to deallocate memory all the time. For example, they often need to store per-frame data that gets dropped when the frame is over, debug strings, track temporary access to texture/buffer data, manage command buffers, etc. They also have to track resources for lots of data types external to the program (such as GPU data) which often forces them to work with external allocators, as well as having to synch…

> For example, they often need to store per-frame data that gets dropped when the frame is over, debug strings, track temporary access to texture/buffer data, command buffers, etc. Exactly. You can just bulk allocate all the memory for the frame at the beginning and drop the entire thing after the frame is finished. This is a very easy case for manual memory management where you have one allocation and one deallocati…

I love arena allocation (which, incidentally, does entail dynamic resource destruction if you use it per-frame, and which is pretty easy to get wrong if you're just storing raw pointers everywhere--so good bye nice use after free properties), but you absolutely cannot just use arena allocation for everything. GPU resources in particular need to wait on fences and therefore can't have a neatly defined lifetime like what you're proposing unless you block your whole program waiting for these fences, while other GPU resources are (like I said) immutable and need to live for multiple frames. Where exactly do you store this data? How do you access it? How do you free it when it's done? How do you perform slow tasks like recomputing pipelines and shaders in the background without being able to dynamically track the lifetimes of the old/new backing GPU allocations? More generally, how do you track resources other than the framebuffer/surface, which are usually managed very dynamically and often appear and disappear between frames?

I think maybe it would be helpful if you would go look at the actual implementation of a nontrivial renderer and try to find one that doesn't allocate. Because the strategies you're describing don't match my experience working on renderers, at all.

Re: Goodbye C++, Hello C

#86
Yesterday I finished a complex library in C++20, which I wrote "Rust style" by following the ISO C++ guidelines. I used 0 `new`, no smart pointers, all by-value returns, move semantics and STL containers. It literally took a month to write, but it worked as expected at the second attempt (first failed due to forgetting a `}` in a format string). I did not see a single segmentation fault and it works fine on both Linux and Windows. Rust and modern C++ give you the expressivity to describe what you want to accomplish to the compiler.

You can use the type system to erase certain classes of error entirely, without ever having to worry about something pointing to bad memory or whatever. If you follow certain rules, C++ can be _almost_ as safe as Rust.

I love C personally, and I've been coding in it for more than a decade now. It is simple, easy to learn, but it gives you literally zero ways to create abstractions. That could be a good thing, until you realize that almost all complex C codebases reimplement (badly) half of the STL due to libc providing no containers of sort. Linked lists are terrible for performance, yet C programs are pestered with them due to how simple they are to implement. And I won't get started with C strings, which are just a historical BadIdea™.

Most C codebases tend naturally to evolve into a mishmash of reinventing the wheel and ugly macro magic. After all it's either that or to risk committing preventable mistakes all over.

That's not without saying that C++ is that better - every three years a new release comes out and fixes certain issues so vastly that it completely changes how you are supposed to write code (see string_view, or concepts). That's not without saying that the language is also full of old cruft no one should ever think about using.

Re: Goodbye C++, Hello C

#88
post #82

Earlier quoted context omitted.

> For example, they often need to store per-frame data that gets dropped when the frame is over, debug strings, track temporary access to texture/buffer data, command buffers, etc. Exactly. You can just bulk allocate all the memory for the frame at the beginning and drop the entire thing after the frame is finished. This is a very easy case for manual memory management where you have one allocation and one deallocati…

I love arena allocation (which, incidentally, does entail dynamic resource destruction if you use it per-frame, and which is pretty easy to get wrong if you're just storing raw pointers everywhere--so good bye nice use after free properties), but you absolutely cannot just use arena allocation for everything. GPU resources in particular need to wait on fences and therefore can't have a neatly defined lifetime like wh…

I am speaking from experience, I have written a lot of renderers.

> GPU resources in particular need to wait on fences and therefore can't have a neatly defined lifetime like what you're proposing unless you block your whole program waiting for these fences, while other GPU resources are (like I said) immutable and need to live for multiple frames.

I don't quite understand why fences are a problem here. As long as you know what frame a resource corresponds to, you just to have to guarantee that the entire frame is finished before reusing. You don't need to know what's happening at a granular detail within the frame.

As far as static resources, that's easy: another arena for static resources which grows as needed and is shared between frames. Most of that data is living on the GPU anyway, so you just need to keep a list of the resource handles somewhere, and you're going to want a budget for it anyway, so there's no reason not to make it of finite size.

> More generally, how do you track resources other than the framebuffer, which are usually managed very dynamically and often appear and disappear between frames?

See the whole issue is trying to solve the issue "more generally". In many cases, you can know exactly the resources you will need (or at least the upper limit) when you start rendering a given scene. If you need to do any memory management at all, you can do it at neatly defined boundaries when you load/unload a scene.

The only time when you need some kind of super dynamic renderer is when you are talking about an open world or something where you are streaming assets.

Most of the serious renderers are written in C++ using custom allocators, which are just some kind of arena allocator under the hood anyway.

Re: Goodbye C++, Hello C

#89

Yesterday I finished a complex library in C++20, which I wrote "Rust style" by following the ISO C++ guidelines. I used 0 `new`, no smart pointers, all by-value returns, move semantics and STL containers. It literally took a month to write, but it worked as expected at the second attempt (first failed due to forgetting a `}` in a format string). I did not see a single segmentation fault and it works fine on both Linu…

Is the library open source? If so, would you share a link?

Re: Goodbye C++, Hello C

#90

> Where C++ gives you many slightly different takes on the same concept (e.g. unique_ptr, shared_ptr and raw pointers), C gives you one way to go and that one has a conveniently compact notation (such as float ). You use unique_ptr and shared_ptr because float is unsafe. If you don't care about the safety that smart pointers provide, you can use float* in C++ too. > ticking with the example of matrix math, it is baff…

FWIW i've seen a lot of 3D codebases in C++ that avoid operator overloading and instead use something like `A.multiply(B).add(c)` to make more explicit what is going on. Some even break that into multiple calls instead of using a single expression.

You'll find a lot of SIMD code using the multiple calls approach as for far too long C++ compilers did daft things like transfer the SIMD registers back to the stack or refuse to inline when the expression got slightly interesting.
Post reply on HN