Monday, May 2, 2011

Finally, An Exciting Project!

Just shot this off to my advisors, and I figure it's accessible enough share here rather than at my (largely ignored) research blog. Hopefully this makes sense to non-CS people. If it doesn't the backtracking posts at said research blog might help.

Using Games to Teach Shared Memory Parallelism

It seems to me that there are two approaches one can take with designing educational games. If you’re simply looking to teach facts (arithmetic, spelling, geography), you can focus on rote skill building. Decades of Math Blaster and Where in the World is Carmen San Diego and who knows what else have made it plain that, if you create a compelling enough environment, you can simply drill basic ideas over and over again and players will eventually learn them. If you’re lucky, they might even begin to pick up on the underlying principles, but this is less essential at the point where such games are generally in play. It doesn’t matter as much if players pursue a trial and error approach so long as they eventually build up the right stimulus-response pairing so that, when presented with “7 + 6”, they spout out “13”.

When teaching concepts, however, things become more difficult. The goal in this instance is to help the player realize that they *already* understand what we’re asking of them by transporting the underlying principles into a familiar environment. This means we’re not just taking some ultimate response into account. The process, and learning how to relate that process to desired domain, becomes the focus. This makes it much harder to properly develop games to teach concepts, since the process and the eventual relationships that form are completely outside of our control. This means that we need to make sure that our games are designed with metaphors that accurately represent the original problem or system and that they main focus is on tasks we actually want to promote.

When we first began discussing ideas for a game to “teach” shared memory concurrency, it was suggested that I work with trains. The basic idea was that trains are exciting and that signals and switches are already built into the system. For a handful of basic ideas, this seemed like a great approach, but as soon as we expand to more complicated problems, it falls apart. Consider, for instance, the most basic problem with shared memory concurrency: race conditions.

For the simplest case, two threads running through the same piece of sensitive code, this makes sense. We can have trains running on parallel tracks that eventually become a single track. If both trains try to do this simultaneously, they will crash. Thus, we need policies to ensure that only one train makes the transition at a time.

Let’s look at a more complicated example, though. Let’s say we have two completely different sections of code, both of which touch the same global variable. Suddenly, our trains are no longer running on parallel tracks. They’re working in completely different regions of space. How, then, do we signify that our two trains need to somehow limit one another’s movement?

Sure, we could introduce new elements -- say, pressure guages rigged to bombs that will cause the track to explode if the weight of more than one train touches any segment to which the sensor is connected. But that starts to expand our metaphor beyond what is reasonable. It makes our metaphor instantly unrelatable, even if somewhat more exciting.

Let’s also consider the basic nature of computer programs as humans write them, which tend to be broken into numerous subroutines. A subroutine is called in one spot, control jumps to that subroutine, and then, when the subroutine is finished, control returns to the spot it which the subroutine was called. This also cannot be readily modeled with the train metaphor, as an actual train moves along a single continuous piece of track. It doesn’t teleport all over kingdom come before eventually reaching its destination.

The underlying problem here is one of metaphor. We have too few types of objects, and they’re related in the wrong way. Let’s take a moment, then, to look at the requirements for an effective metaphor for a shared memory system:

First of all, let’s consider the things that need to be modeled. Obviously, we need multiple actors that can represent our threads, and we need something to guide the behavior of these actors to represent our code. The train metaphor has both of these in the trains and the tracks. It is important to note here that the tracks in the train metaphor can only represent the program, since they fundamentally guide the behavior of the trains. We need more than this, though. We need to be able to see why shared memory, in particular, is difficult. This means that our metaphor needs to contain some way of representing memory and another way of representing the values in that memory so that we see why things are going wrong.

Moreover, since threads operating in a shared memory environment can see virtually any piece of the shared address space at virtually any time, we need this memory to be obviously accessible to our actors. Between this necessity and the obvious requirement that memory somehow contain values, it becomes obvious that memory must be a physical space and not a logical one in our metaphor. Our program, on the other hand, just needs to be some sort of set of instructions that the user can see and augment.

Note that I didn’t include any sort of signals or locks or control statements of any sort as necessities for our basic metaphor. That’s because these control statements are primarily what we want to teach. This means that they are more effective if we highlight them by making them additions to our otherwise familiar situation. Much of the problem with programming shared memory concurrency is that it isn’t ultimately intuitive why we need these sorts of constructs. Our primary experiences of concurrency are physical, and the physical world places obvious, unviolatable limitations on what can and cannot be done in parallel. Chiefly, two objects cannot occupy the same physical space (though I know a handful of metaphysicians who disagree under certain circumstances).

Our goal was always to make control of these constructs one of the chief mechanisms of “play”, and by injecting the constructs into the situation as somehow unintuitive and alien, we increase the strength of the relationship between our metaphor and our “real world” circumstances.

If all that we were trying to teach was concurrency, this would be sufficient, but we’re looking to teach parallelism, the use of concurrency to provide improved performance via domain decomposition. This means that we want to place one more restriction on our metaphor. We want our “instructions” to be something that the player can actively divvy up between our actors to see how different approaches work. This was, in my opinion, another failing of the train metaphor for this particular problem. Establishing conditions for different signals to be sent between trains doesn’t do anything to modify the way the routes themselves operate. There is no obvious domain to decompose.

With all of these considerations in place, I would like to suggest the basic framework for a new game to teach parallelism in a shared memory environment, one I like to call Too Many Cooks!

The basic idea is that you control a kitchen with several cooks, all working together to produce enough food of a minimum quality to satisfy a given number of customers within a certain time limit. You begin with a certain number of cooks and a list of recipes. Recipes are divided into steps, and each recipe has its own quality ranking and will feed a different number of people.

At each level, your goal is to select recipes that meet or exceed the level requirements and divide the work of preparing them up among your chefs (by assigning either whole recipes or tasks within recipes to individual chefs) so as to finish under the time limit. At the end of each level, you recieve a score based on the average quality of your food and how far under the time limit you came.

So, we have our two foundational ideas that make this a metaphor for a (parallel) computer program: our actors and our instructions. We also have the task of domain decomposition. What makes this a shared memory system, then, are the limitations of physical space.

The kitchen is divided up into regions for different tasks. Counters for chopping. Ovens for baking. Stove tops for simmering. Freezers for cooling. Chefs will retrieve ingredients (values) from some bottomless pantry or from other workstations and transfer them around from chef to chef and station to station as the recipe is completed. Every time you assign a chef an instruction, you tell him where (s)he will find the necessary ingredients and where (s)he will work at the task. However, our chefs, much like computers, are a little bit stupid and do not communicate directly.

This means that, if Chef A needs onions that Chef B is chopping, but Chef B is not finished chopping them, Chef A may suddenly find himself fingerless as he intrudes on Chef B’s activities and the level will be failed. Similarly, if Chef A is expecting tomatoes at a given workstation, but Chef C hasn’t yet gotten around to preparing them but the cucumbers that Chef D had just finished up for Chef E are still sitting there, Chef A will gladly take them, ruining his recipe and resulting in unhappy (vomitting) customers and another failure of the level. There can be all sorts of other situations like this where things go wrong because of a lack of communication and timing.

What this means is that players needs to do two more things. First of all, they must be intentional in their assignment of tasks between chefs in order to minimize such conflicts (especially of the second sort described). More important, though, it is their job, after assigning tasks and locations to the chefs, to annotate the recipe with a number of markers that result in indirect communication between chefs. In the first example, for instance, Chef B could put up a sign that would prevent other chefs from intruding on his space.

Again, it would seem somewhat non-intuitive to players why they would need these signs. In the real world, chefs wouldn’t be so stupid. But that’s the entire point. It makes the task memorable and thus, ideally, helps in its transfer to the actual domain of writing programs.

The game will begin in a small kitchen with only two chefs and requirements that allow them to work on individual recipes with no communication in order to simply pass. But, as time progresses, you will unlock a larger stable of chefs and more elaborate recipes and requirements will mandate complicated distribution of work and consistent use of signals in recipe preparation. This larger grouping of chefs and recipes will remain available for the player to go back and complete earlier challenges with more elaborate approaches for a higher score.

There are lots of specifics yet to be worked out, and I feel like I’m neglecting to mention things that I’d already thought of. Even once I figure out all of the foundational ideas, development of this game will not be a simple task. Tweaking and balancing everything in this system from level requirements to recipe complexity to interface in order to make it engaging and reasonable is going to be terribly difficult, but that’s a problem faced by any game devloper, particularly, I would imagine, those working on educational games. Still, it seems to me like a solid beginning point, and I would welcome any criticisms or suggestions for its improvment as I get things hashed out on paper and start to figure out a development platform.

6 comments:

Anonymous said...

How would loops work in the kitchen game? For example, I can set up a loop that runs for i=0-9, but in this example the values -- ingredients -- are not clearly hierarchal. That is, the statement i=i+1 means nothing when it's cucumber=carrot+1.

Walz said...

I should have noted that the goal here isn't to teach programming, just the thought process for parallelizing work in an environment that we twist to behave somewhat like a computer. Because of that, I'd suggest that we don't need instructions that perfectly resemble a computer program, we just need some algorithm that can be divided up among multiple actors. I'd argue that a recipe meets those criteria.

Building a game to teach programming generically is somewhat harder due to the simple fact that a well written program should be generalized as much as possible. Virtually any real-world set of instructions is going to be relatively concrete. In other words, our instructions essentially represent the runtime state of a program, where all of the values are known, rather than a compile time state.

The closest we would get in this case would be some task that needs to be repeated several times over the course of one recipe (or even that is called for in several recipes). Depending on the complexity of the task and how the output is used, this could be parallelized in a number of ways.

For one example, you could have each instance of the task be performed by a separate chef, possibly having several of them perform the same task concurrently. This might be best if one recipe called for you to, say, french several racks of lamb. Alternatively or you could have a single chef acting as a "server" of sorts to whom the other chefs repeatedly go for the end product of the task. This approach might be best if you have several different recipes that all require onions at different points.

In other news, if you're writing a loop from i=0:9, as opposed to i=0:n where n is some variable, you're doing something wrong. :P

Anonymous said...

So to what level is the game a CS game different than something you might find on addictinggames.com? My original impression is that this would be designed for CS students to apply what they already know about programming into a fun way of running parallel processes. That could be made fairly high-level (where you drag and drop commands into each chef's timeline) or fairly low-level (where you write code to dictate the chef's moves).

Ha, ok perhaps the loop should be i=0,n. You (and most CS folks) would abhor the way I write code.

Walz said...

Yay for good questions! Especially questions that contain some of the same assumptions that I suspect my advisors share!

First of all, I don't think that you can make a good educational game without having it be a good game first. Honestly, if I could sell this on the Android Marketplace or put it on Addicting Games and have normal people play it and enjoy it without necessarily realizing that they were practicing skills that are useful to Computer Scientists, I'd be ecstatic.

Second, again, the goal isn't to teach *programming*, but parallelism -- the effective division of tasks among multiple actors in order to gain speedup. I feel like mixing the two is asking too much and also pulls you out of the metaphor. Recipes are concrete. You know the ingredients and process before you begin. We just want to have players optimize that preparation through a series of markups.

Often times, parallelization of real world algorithms works this same way. We take a known approach and modify it to make use of the potential for concurrent execution. True, this is often done by dividing up loops and other high level programing constructs, but I see a lot of value in encouraging people to think about more fine-grained, communication heavy forms of parallelism.

You have given me one interesting idea, though. We could offer a mode alongside the main game where players can "code up" their own recipes for use in game and then we can rank them be a series of metrics, providing them a quality and determining how many people it feeds.

benpost said...

"Sure, we could introduce new elements -- say, pressure guages rigged to bombs that will cause the track to explode if the weight of more than one train touches any segment to which the sensor is connected. But that starts to expand our metaphor beyond what is reasonable. It makes our metaphor instantly unrelatable, even if somewhat more exciting."

If you find a way to put pressure plates and bombs in the kitchen, I will buy a thousand copies of your game.

Jonathan Lovelace said...

Now I want to play this game. And you haven't written it yet---though it reminds me slightly of one of the games that came with Windows (Vista) on my current laptop.

But I think a slight narrowing of the milieu would improve the concept: instead of just "a restaurant kitchen" with chefs, make it a fast-food kitchen. That would explain how the chefs could be competent enough to prepare dishes the customers order, but clumsy enough to slice each others' fingers off. If they're minimum-wage burger flippers in a fast-food place (and one too small to have machines to cut up ingredients, at that), such clumsiness is only to be expected.