5 ms·
Isn't this basically saying that you can't have the compiler guaranteeing you aren't including bugs in those optimizations / global variable usage because it's
by jrsj 6y ago
Isn't this basically saying that you can't have the compiler guaranteeing you aren't including bugs in those optimizations / global variable usage because it's more efficient to just write and then ultimately ship some bugs?
To me it seems like this would require a significant adjustment in how certain problems are approached, but the outcome would likely be more effective development as you could eliminate a lot of cost that's spent dealing with bugs in the future.
- penagwin 6y agoI'm not familiar with rust or if their `unsafe` block mitigates the issue somewhat - I know that for example with memory management, you'd rather ship a game that leaks a bit of memory (since games are generally only open for a few hours at a time, not a lot of memory should be leaked) in exchange for less stutter/performance issues from the GC.
- steveklabnik 6y agoLeaking memory is safe in Rust. There is also no GC.
- coddle-hark 6y agoNo, it’s saying that you can do clever memory optimizations that the Rust compiler won’t let you do.
- steveklabnik 6y agoWhat kinds of things, specifically?
- kbenson 6y agoActually "won't let you do" or "makes you clearly mark where you did those things"? That is, are you referring to things it doesn't actually support doing that you want/need to do, or just that it's unsafe?
- efficax 6y agoyou can do all of that in `unsafe` blocks
- mlindner 6y agoYou have full access to the memory in Rust. There's nothing in Rust that prevents you from doing those things. You can even do in-line assembly.
- nine_k 6y agoUnfortunately, Rust has limitation on what it can prove to be safe using the ownership and lifetimes approach. One of the simplest things which can't safely be done in Rust is a doubly-linked list. I'm certain that Rust will help some parts of game development a lot, but I'm also certain that game developers sometimes use tricks safety of which cannot be proven by Rust's type system.
- steveklabnik 6y agoYou can do these things safely, you just can't do them safely and efficiently at the same time. You could do a doubly linked list with reference counting, and it would be in safe code, it just wouldn't be classic implementation you'd be imagining, and not as efficient.
- worik 6y agoYou can do circular structures like doubly linked lists in Rust. You can do them efficiently. But you do need to use "unsafe". Of course you do, a moments thought shows you why. It is not as "safe" as Rust without "unsafe" blocks, but it is still reasonably safe. But circular structures are usually a bad choice.
- scythe 6y ago>Unfortunately, Rust has limitation on what it can prove to be safe using the ownership and lifetimes approach. One of the simplest things which can't safely be done in Rust is a doubly-linked list. It can be done "safely" in Rust, as in without actual bugs; it can't be done in "safe Rust", that is, Rust without `unsafe{}` blocks. But it's reasonable to assume that any Rust game would make liberal use of `unsafe{}` blocks, because as we have all agreed, game developers don't actually care that much about memory safety anyway. As such it seems wrong to suggest that the primary barrier to Rust in games development is its memory safety. Perhaps the culture of today's Rust community that shuns `unsafe{}` is unsuitable for games development, but Rust offers plenty of advantages beyond memory safety, and in contrast with C++ the particular advantages I would see as most relevant are: - no legacy types/structures, important when you have a large team on a shoestring budget working late nights with sporadic communication; there will be fewer discrepancies in code styles - potentially faster compile times than C++ when Rust is more mature - distant future item, but there's the possibility for better FFI with game scripting languages when Rust is more mature None of the above really depends on memory safety except kinda the FFI, but it's all relevant for a game dev. While the adoption of "safe Rust" has left the tarmac, the best practices and widespread use of "unsafe Rust", a perfectly reasonable language in itself, have barely begun to develop. It's likely that any Rust games will generally be written with lots of `unsafe{}` and while nobody would learn Rust to use `unsafe{}` specifically, game developers might learn mostly safe Rust in CS courses that cover systems programming and then take those skills to the game industry and already know most of what they need to ramp up on an unsafe Rust codebase. That last sentence is also why I think the Rust games era is at least a decade away; game shops don't usually want to retrain their employees on new languages; they'll just use what's in the field. That's also why Rust will probably beat out Zig (or Nim/Jai), since even though the latter is a little more suited for games, the former is much more likely to be taught in schools.
- naikrovek 6y agoGame development problems are very often global state manipulation problems. In a given game, many things may have genuine need to be able to read and write to multiple global variables once per frame (that's usually somewhere between 30 and 240 times per second), and that is after all unnecessary global state has been removed. Enterprise developers often look very poorly upon the solutions that game developers come up with, but game development is an entirely different realm, very far away from any enterprise software development stuff I've worked on, or even heard of. I've looked. I didn't believe this myself for a long time. Games have very strict performance requirements that enterprise software simply does not have. Virtually no one in enterprise software cares about performance, really, and certainly not as a primary concern. Enterprise developers will often just throw more RAM or CPU at the problem until it goes away. I mean why not, that's an option that is on the table. Of course they're going to take that route sometimes. Game developers can't do this, because they're not in control of the hardware that their game is run on. Games are often scored on how good they look, and how well they perform. Game sales are a function (at least partially) of the score/rating that the title attains, and game developers are paid out of the money that game sales provide. Concerns that do not result in an improved critical game rating are secondary. So, there is a very strong hunger there for performance which simply does not exist in other software development fields. Until enterprise software developers are paid based on the performance of the applications they write, no one who has NOT developed a game and fought performance problems along the way can ever understand. There are games which consume nearly the entire bandwidth between CPU and RAM on a modern PC -- continuously -- and this is after many optimization passes to prune the data that is needed from RAM at any particular moment in time and make it as small as possible. Game development is just an entirely different thing than any other kind of software development. Languages created with general purpose use in mind may achieve (and have achieved) some success in game development, but those languages will get in the way of the developer at least as often as they aid the developer. Same rule applies for general software engineering rules.
- bsoft16385 6y agoThe one thing I will note is that most of what you have said about performance is relevant primarily in the AAA game space (and in higher end mobile titles, which increasingly look like AAA games). In the indie space, you see a lot more titles where performance is a secondary consideration. The concept (and the style of the visuals) is often more important than having cutting edge graphics. There are also segments of the business that are more like enterprise software. If you look at something like League or WoW, you have titles that are continuously evolved over many many years, and getting the infrastructure right matters a lot.
- pharke 6y agoThat's counter productive when it comes to gamedev. Most of the code you write for a game is going to be thrown out at some point. Being able to get the idea onto the screen and iterate rapidly is worth a lot more than not having a dev build crash after a while. Make it work, make it right, make it fast except the last two are usually transposed in gamedev.
- HelloNurse 6y ago> iterate rapidly is worth a lot more than not having a dev build crash after a while You are missing the part about wasting hours or days to investigate the crash, easily offsetting the time saved with fast and loose coding. It would be nice to see examples to judge whether a stricter language like Rust can save more time on the debugging and fixing side (by reducing mistakes) than it costs with verbose syntax and "unnecessary" safety mechanisms on the design and coding side.
- pharke 6y agoYou only have to debug the code if you're going to keep it. I'm not going to spend days debugging a prototype made of spaghetti, it'll have served its purpose and I'll start over with optimization in mind and refine the idea. If you wanted a programming language that would be a real win for game development you'd choose something that had the option of no type or memory safety and no boilerplate for prototyping with an option to turn those options on for when you actually integrate the feature with the rest of the code.
- ridiculous_fish 6y agounsafe is sometimes necessary because Rust's safe APIs are needlessly inefficient. For example, consider a function for 16 byte equality: fn equals(v1: [u8; 16], v2: [u8; 16]) -> bool { v1 == v2 } rustc emits a branch to test if v1 and v2 point to the same thing, which is unnecessary (it can never be true!) and can ruin loop vectorization, etc. The useless branch is not serving any safety purpose but requires unsafe to avoid. https://rust.godbolt.org/z/6973jG https://rust.godbolt.org/z/6973jG The efficient version is quite a bit more verbose :(
- RomeoDelta 6y agoI know this is only responding to your one example, and perhaps your statement is still true generally, but I think that in this particular case it is not. In this godbolt [0] I tried using the safe version of equals with two values (from stdin so it can't just optimize the computation away, though I don't think it would) and it seems that when the compiler inlines the equals function, the inefficiencies are removed (which can be seen in the example::main section of the output; lines 212-216 look nearly identical, if not identical, to equals_unsafe in your example). Please let me know if I'm missing something or if there are any other safe APIs to be wary of. [0] https://rust.godbolt.org/z/W8dqaK https://rust.godbolt.org/z/W8dqaK
- ridiculous_fish 6y agoRight, the branch may or may not be optimized out; probably it depends on llvm's alias analysis. That's not good, it means you might break it accidentally. IME Rust is more dependent than C++ on these fragile optimizations. I think it is because idiomatic Rust is more abstract, and also because C++ has more advanced specialization features which allows implementing optimizations directly. For example in C++ you can say "if the type is uint8_t then do this instead..." but this is harder in Rust. You asked for other examples: consider a simple operation over an array, like bitwise not. A C-style for-loop does the obvious thing but idiomatic Rust emits a gazillion one-byte 'not' instructions. This doesn't involve 'unsafe' but it does illustrate that you really do have to babysit Rust to get efficient codegen. https://rust.godbolt.org/z/PjW5vo https://rust.godbolt.org/z/PjW5vo (In fairness C++ has its own stupid pitfalls too: https://travisdowns.github.io/blog/2019/11/19/toupper.html https://travisdowns.github.io/blog/2019/11/19/toupper.html)
- megameter 6y agoMost of the remarks here emphasized performance, but I believe the actual thing that is "lost" in Rust's model is the ability to quickly rearrange the data dependencies. A lot of the time, a game's bug surfaces in a way that will never cause a crash: it's just undesired behavior. At some level, the behavior can be defined in terms of "must" and "cannot" constraints, RFC-style. And in theory you could do some kind of automatic verification of each behavior. But...the majority of the plumbing of the game isn't in the rule logic itself - almost all of the rules are simple. It's in making sure that the mutations produced by one rule will flow neatly into the next, without introducing some form of synchronization bug. This is a real minefield because you're often iterating over lots of similar elements, and it can look like you can bundle their update code to look "clean" or present an opaque interface, but then in practice, you need to split out the iteration differently to resolve a dependency issue. Developers responded to this by introducing an event-bus abstraction, with bubbling and priorities and all the bells and whistles. But that doesn't really solve having dependencies, it just presents another way to surface a scripting layer. Repeat this class of problem enough times and you will start inlining the bulk of the update into a single main loop, because that makes manual review of the dependencies more of a spatially engaging process, one where you don't have to provide new boilerplate so that the build process can tell you that you screwed up: instead you just look carefully, see that the ordering is wrong, and shovel around the block of code. Done. And while you can do this in Rust, it renders a great deal of the language irrelevant, because the features it helps most with are those parts where you want tighter access control across function boundaries. When you start leveraging that access model, behavioral changes can start to take hours instead of seconds, which makes it appropriate for inner-layer backends that need deep optimization, but ergonomically terrible for the greenfield case, which is what games need to bend towards early in production when major features might be changing.