Archive for May 2007

18

May 2007

.NET Using Dictionaries for a Performance Boost

I often see code that loops through List<T>s or uses List<T>.ForEach() to repeatedly find specific items in a list. Take the below example. Rather than hitting the database every time we need to check permissions, they're all loaded upon login and cached into the session. As the user moves around the application, the permissions are checked with code similar to the below.

/// <summary>   
/// A cached list of posts with their access levels   
/// </summary>   
List<Post> posts;   

/// <summary>   
/// Preload the posts for testing permissions   
/// </summary>   
private void preloadPosts()   
{   
	posts = DataAccess.Post.GetAll();   
}   

/// <summary>   
/// Returns the access level for a given post   
/// </summary>   
/// <param name="postID"></param>   
/// <returns></returns>   
public AccessLevel GetAccessLevel(int postID)   
{   
	// Loop through each post looking for the access level   
	foreach (Post p in posts)   
		if (p.ID == postID)   
			return p.AccessLevel;   

	// If we didn't find a post, we don't have access to it   
	return AccessLevel.NoAccess;   
} 

While this isn't a big deal if the list is small, or the method isn't called much, what started off as simple code could quickly grow.

I was asked to look at the performance of one our applications, and requested the company invest in RedGate Ants Profiler (which I'd highly recommend for anyone doing .NET development - shame there isn't a cheaper Home Edition!). When I ran the profiler it became very obvious very quickly where the problem was. The problem was that our application was taking several minutes to return a single list/table of data. The database had two tables: Person and Answer. The Answer table had a PersonID, and each Person could have hundreds of Answers. There were hundreds of People.

The offending code looped through all People, and then did a .FindAll() on a List<Answer> containing every answer for every positions. This mean with 10,000 positions, each having 1,000 answers, the List<Answer> had 10,000,000 items. This meant we were calling .FindAll() 10,000 times on 10,000,000 items. That's 100,000,000,000 iterations.

Since I wasn't able to modify any of the data access code at the time, I had to work with the two lists (List<Person> = 10,000 items, List<Answer> = 10,000,000 items).

Rather than loop through the 10,000,000 items for every position, I decided to loop through them once. By adding them to a dictionary based on their PersonID, I could quickly retrieve them while looping over the people.

The new code looked like this:

public void OutputData(List<Person> people, List<Answer> answers)   
{   
	// Create a dictionary to looup answers in   
	Dictionary<int, List<Answer>> answerLookup = new Dictionary<int, List<Answer>>();   
  
	// Loop through every answer   
	foreach (Answer a in answers)   
	{   
		// If we haven't hit this Person before, create a lookup for them   
		List<Answer> myAnswers;   
		if (!answerLookup.ContainsKey(a.PersonID))   
		{   
			myAnswers = new List<Answer>();   
			answerLookup.Add(a.PersonID, myAnswers);   
		}   
		else // Else get the existing list   
			myAnswers = answerLookup[a.PersonID];   
  
		// Add this answer to our lookup   
		myAnswers.Add(a);   
	}   
  
	// Now output our data   
	foreach (Person p in people)   
	{   
		// Get a list of ONLY the answers for this position   
		List<Answer> myAnswers = answers[p.PersonID];   
		Console.WriteLine(p.Name);   
  
		// Loop through our answers   
		foreach (Answer a in myAnswers)   
			Console.WriteLine("	{0}", a.AnswerValue);   
  
	}   
}  

The resulting code took seconds to run instead of minutes, and the time spent on those loops was now only a small fraction of the total time (the database calls took most if it).

So, next time you find yourself writing a .Find() call or a .ForEach() to just find a particular element, consider the performance implications, and maybe add a Dictionary to make things faster.

17

May 2007

Loading Models in XNA with the Content Pipeline

The last time I played with XNA was when it was in in beta. There was no "Content Pipeline", and loading models was so tedious, I just decided to stop using XNA until it RTM'd. Since now is that time, I thought I'd best see how things have changed...

I bought the GarageGames Orcs RTS Buildings Pack, since it looked fitting for an MMO, was pretty cheap and had .x versions already included (which I know the Content Pipeline should handle).

I fired up GSE and added the .X files to my project and clicked Build....

I was greeted to some compile errors - and I hadn't even started coding!!

Thankfully, these errors were trivial. The content pack included .x and .jpg files with the same name (orcs1.jpg and orcs1.x). The content pipeline likes to strip the extensions off for the asset names, so there was a clash. I ran through and added ".jpg" to the asset name of each jpeg (though now I'm typing, I'm thinking I could've just Excluded them from the project, since the reference in the .x file would cause them to be added!).

With them all building, it was time to see if I could add them to my world. I was starting with the ChaseCamera sample from creators.xna.com, since I knew I'd be able to move the camera around easily to see the models.

At the top of Game.cs, I added a new variable to hold my models:

List<Model> buildingModels = new List<Model>();

And in the LoadGraphicsContent() method, I loaded each model. Since the filenames weren't sequential, I just hard-coded them for now:

buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs1"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs11"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs12"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs13"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs14"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs15"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs2"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs3"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs4"));
buildingModels.Add(content.Load<Model>("Content/OrcPack/snow/orcs5"));

And in my Draw() method, I called the existing DrawModel() method (which came from the ChaseCamera sample, to draw the ship/ground). Since the models are in a much smaller scale than the ship, I had to scale them up. Really this should be done per-asset at compile time - I'll see about creating a custom importer for this later (since all content packs will likely have different settings, and I want my units to be metres).

DrawModel(buildingModels[0], Matrix.Identity * Matrix.CreateTranslation(new Vector3(0, 0, 0)) * Matrix.CreateScale(100));
DrawModel(buildingModels[1], Matrix.Identity * Matrix.CreateTranslation(new Vector3(300, 0, 0)) * Matrix.CreateScale(100));
DrawModel(buildingModels[2], Matrix.Identity * Matrix.CreateTranslation(new Vector3(600, 0, 0)) * Matrix.CreateScale(100));
DrawModel(buildingModels[3], Matrix.Identity * Matrix.CreateTranslation(new Vector3(600, 0, -300)) * Matrix.CreateScale(100));
DrawModel(buildingModels[4], Matrix.Identity * Matrix.CreateTranslation(new Vector3(600, 0, -600)) * Matrix.CreateScale(100));
DrawModel(buildingModels[5], Matrix.Identity * Matrix.CreateTranslation(new Vector3(300, 0, -600)) * Matrix.CreateScale(100));
DrawModel(buildingModels[6], Matrix.Identity * Matrix.CreateTranslation(new Vector3(0, 0, -600)) * Matrix.CreateScale(100));
DrawModel(buildingModels[7], Matrix.Identity * Matrix.CreateTranslation(new Vector3(0, 0, -300)) * Matrix.CreateScale(100));
DrawModel(buildingModels[8], Matrix.Identity * Matrix.CreateTranslation(new Vector3(200, 0, -200)) * Matrix.CreateScale(100));
DrawModel(buildingModels[9], Matrix.Identity * Matrix.CreateTranslation(new Vector3(400, 0, -400)) * Matrix.CreateScale(100));

I also made a slight change to the ground.x model to reference the snowy ground.jpg from the content pack. I ran the project again, and there are the models!

So, in conclusion, the Content Pipeline makes loading models trivial. The next few things to look at are heightmaps, loading models in from some sort of "level file" rather than hard-coding, and animated models that react to user input (eg. breathing/running animations).

12

May 2007

Using iPod with Windows Media Player instead of iTunes

Up until recently, I've never had an iPod. It wasn't that I didn't like them - I think they're the best looking MP3 players out there. There was one reason, and one reason only that I hadn't bought one - iTunes. On Windows, and it has to be amongst the clunkiest applications from a major software company I've ever used. I'm not sure if it's because of the desire to make it look like a Mac and custom-draw everything, or just sloppy coding. It just always seems clunky and unresponsive, and hogs machine resources.

To make things worse, Apple started telling people not to upgrade to Vista because iTunes didn't work properly on it. WTF! Apple had how long to sort this out? You can't just tell people not to upgrade their operating system because you failed at meeting the deadline for your music player! (I do see this has since been fixed).

Anyway, back to the point. What stopped me buying an iPod was iTunes. The software is nasty, and I already have my music collection in Windows Media Player, which plays nice with Windows Media Center and my Xbox 360. If Apple made the iPod work with Windows Media Player, I'd have bought one. Assuming there are other people like me, Apple are losing potential business by trying to force people on to iTunes. I found there were a few ways to get Windows Media Player to work with the iPod, but it required 3rd party plugins (and cost money), and I wasn't about to buy an iPod to find these solutions aren't stable.

As fate would have it, I received a Blue 4GB iPod nano from my auntie when she was visiting from the states. The whole idea of not wanting to buy one in case it didn't work was squashed. I had an iPod, and I was going to use it, with or without Windows Media Player support!

When I got home, I downloaded a trial of MGTEK dopisp - one of the plugins claiming to get the iPod working with Windows Media Player. There are a few other plugins to do this, but a quick Google revealed less unhappy people using this one! I installed the plugin, connected my iPod, and fired up Windows Media Player.

Error! I was greeted with a dialog telling my my iPod had never been set up (via iTunes) and couldn't be used. I had a feeling this would happen, and luckily I had a laptop running Windows XP I was about to flatten! I installed iTunes and set up the iPod, then tried again. The iPod now appeared (with the name I assigned in iTunes) in Windows Media Player as a mobile device. I grabbed a few songs and tried to sync. It worked. It worked exactly like I wanted it to. It was that easy.

That makes me wonder why Apple didn't write a similar plugin? I understand they really want people to use iTunes, but is it really worth losing iPod sales over? Forcing people to use your software is not the way to do business. Sell your iPod on what it is. Sell iTunes on what it is. If people just want one, let them have it. You're lucky I received an iPod as a present, because you'd have missed out on this sale without native Windows Media Player support.

04

May 2007

Visual Studio 2008 Beta 2 First Impressions: WPF Applications

Visual Studio 2008 Beta 2 is now available. That means all of the cool things Microsoft have been tempted us with for the last few years are available to use: Linq, Linq to SQL, Lambda Expressions, (more stable) WPF Designer!

Something I didn't realise until now, is that the "WPF Application" option hasn't replaced the old "Windows Forms Application" option. You can still create .NET 3.5-targeted, good old Windows Forms. As it turns out, it's a good job, because the WPF designer just doesn't cut it. Here's some things I noticed within 5 minutes of trying to build a simple WPF App:

  • The property grid doesn't have an option to show Events
  • The property grid doesn't have an option to sort alphabetically
  • There's no drop-down above the property grid showing all the objects on your forms
  • There are massive rendering issues in the designer on my machine (Vista)

Being the final beta and with a Go-Live licence, I was hoping this was all going to be pretty stable, but it really isn't good enough. Sure, the property grid looks like a complete rewrite in WPF, and I admire them for that, but unless it's going to ship working at least as well as the old one, is it really worth it?

The final point, about rending issues is quite a bug. Sometimes controls just don't appear on the form until I click where they should be. If I run my application and then close it (so VS gets focus back), the form is completely blank. Waving the mouse around clicking reveals all of the controls.

All in all, I'm not impressed with the WPF Designer, and I won't be building my next application in WPF!

I don't know how Microsoft choose to categories their properties, so I always use A-Z view. When I've got controls obscuring other controls, I used to use that nice drop-down. Why must I know switch to XAML just to find my control? Why must I go via XAML to see my event handlers?

Let's hope this changes before VS2008 ships!!