Tim Sweeney and the Future of Game Development I
If you've been paying attention to anything Sweeney's been saying over the past year or two, this will all be very familiar. If you haven't, then I suggest you do, because as far as I can tell he's the only high profile guy in the industry who is actually trying to move us out of this ridiculous stone age which we are currently forced to suffer in when developing games. I don't mean to suggest that his ideas are necessarily the best way of moving forward, but the goal is to effect change, and that's really the biggest step.
There are two basic and somewhat related problems which Tim is concerned about for game development. The first is code correctness. He wants to be able to write code which is very heavily annotated with constraints, both on the values of variables and on the variables themselves. For example, a possible constraint on an array variable would be that it cannot be a null reference. A constraint on the value of the array would be that all its elements be unsigned integers less than 10. (These constraints can be dependent, too; the upper bound of those integers could be the last index of another array.) The compiler would statically enforce these constraints as much as possible, and presumably emit checks for the rest (he's always been kind of vague on that part). He mentioned, as he has before, that about half of the bugs in Unreal can be traced to null pointer and out of bounds memory accesses, integer overflow, and use of uninitialized variables. His ideal is a world where, as much as possible, when the compiler reports 0 errors and 0 warnings, the code is correct.
The whole correctness thing actually reminds me quite a bit of Spec# which is a really cool little language that I've been meaning to mess with for some time. It's worth reading the papers they have on that page, since they talk at some length about their static verification, which includes using a theorem solver to prove certain assertions about the code. It's really very cool that we can do that sort of thing, and quite unfortunate that it hasn't really made its way into the mainstream yet. (Although since Spec# is for .NET, we can blend it happily with anything else, which is pretty slick.) As I've said in the past, I am a big believer in static checking -- although the full explanation is rather difficult to lay out concisely, which is why I haven't finished that series yet. To cut the story short, I am very much on Sweeney's side here, and have considered switching from C# to Spec# outright for my hobby stuff on more than one occasion.
The other big problem is concurrency. It's obvious that more cores and more threads is going to be the way to scale from here on out. (My personal take, and I suspect that Tim would agree, is that the way forward is symmetric multiprocessing. PS3 style architectures will not be welcome or appreciated in years to come.) The problem is that we don't know how to scale up past more than just a handful of cores. 2 cores was pretty easy. 4 and 6 have been tricky but we've managed it to some extent. Beyond that, we're out of ideas that don't change the programming model. How exactly do you write a game that makes efficient use of 16 cores or more? We need to move beyond the current model and find a better way of handling concurrency if we're going to accomplish anything.
Tim gave an analogy I quite liked. Essentially he explained that locks (and atomic operations like CAS) are equivalent to manual memory management. We don't really want to do either, and removing them from the equation is a big step forward for programmer productivity. (He cited a figure of about 30%-40% less time to produce equivalent code in C# instead of C++, including bug fixing time.) Just like modern languages took over control of memory management, the next round of languages needs to take over control of concurrency, specifically the handling of shared data during concurrency. (Splitting up computation across threads automagically is a far more touchy thing and although things like OpenMP are neat, it's not something we'll have in a usable way any time soon.)
Sweeney's analysis of the situation agrees with the conclusion I came to some months ago, which is that in the long term, software transactional memory (STM) is the most likely to yield workable results. Message passing concurrency, similar to Erlang, is an interesting and useful approach in many situations, but it simply isn't effective in games, where a single update may need to touch quite a lot of different objects in order to complete. I'm not going to go into detail about STM because there's already plenty out there, I will summarize it. Essentially, STM allows us to write to shared state as if it were a single threaded program, with guarantees of atomicity inside a block. If anything goes wrong in the meantime -- for example, if another thread comes in and stomps all over our memory -- the underlying runtime backs out the atomic transactions and re-runs them one at a time so that they complete successfully. The basic idea is that although you have a lot of threads touching a lot of memory, it's very rare that two threads will touch the same memory at the same time. As long as we handle that situation correctly when it comes up, we can spend most of our time assuming it won't happen.
STM is awesome because it doesn't really require much thought. Any time you want to do something with shared state, any shared state, you simply open an atomic block and continue as usual. No locks to keep track of. No danger of deadlocks, livelocks, race conditions, or any of the other nasty things that we deal with in lock based concurrency. Things just work. The downside, of course, is that performance is non-optimal. We have a runtime living under us that is doing all kinds of maintenance and tracking work in order to be able to do things like roll back transactions, not to mention we can't do anything atomically that can't be reversed (like IO) and are forced to stick to locks in those situations. But the thing is, that stuff doesn't matter. STM is a constant overhead; it doesn't become significantly more expensive as the number of cores increases. Programs using STM, however, scale fantastically well. If you take a 40% performance hit by using STM on a dual core system, that really sucks, because you're nearly back to single core performance. But if you gain 50% every time the core count doubles, then when you go to quad core you're at 90%, and by eight cores you've crossed the threshold where it's a win. So although STM is a fairly poor choice in situations with less threads, it's likely to be a really great solution five to ten years from now, especially as we get better at implementing STM runtimes. (Remember how much Java performance sucked back in the day?)
Naturally these are all ideals. The reality of the situation is grim, because the industry is critically C++ locked and there's no clear language to move to. It doesn't look like core counts are going to wait for us, and whoever figures out how to leverage all that power instead of simply trying to keep up is going to blow the competition out of the water. The people who are determined to stick to C++ -- or even C (!) -- are dinosaurs that will become increasingly undesirable in the years to come. The only thing holding us back is that nobody's managed to really agree on where to go from here; we just know we need to go somewhere. The current development methods aren't sustainable, and it'll be interesting to see where we end up after we jump off this particular sinking ship.
Jack