69
\$\begingroup\$

What pitfalls did you encounter when writing games for the PC with a managed Language like C# and how did you solve them?

\$\endgroup\$
6
  • \$\begingroup\$ This question is better asked on Stack Overflow, as there is little about it that is specific to games. \$\endgroup\$ Jul 15, 2010 at 13:33
  • 31
    \$\begingroup\$ @Chris: Strongly disagree: The question specifically mentions games! The issues you encounter when you've got to push an update every 16ms are very different to what you'd get in most desktop applications. \$\endgroup\$ Jul 15, 2010 at 14:23
  • \$\begingroup\$ The question is quite unclear. Java and C# differ enough for only very general advice being applicable to both. All the answers so far have been C#. Also target platform not mentioned - good advice may differ depending on the device (e.g. programming for a mobile phone, zune, xbox, different from programming for a pc). It has even been a wide enough question that someone has answered that managed languages themselves are the "pitfall". \$\endgroup\$ Jul 20, 2010 at 21:30
  • \$\begingroup\$ @paulecoyote: I changed to question to ask only about C#. Also, as there is no specific platform mentioned, it's about the PC. \$\endgroup\$ Jul 21, 2010 at 5:17
  • \$\begingroup\$ @Michael assuming platform is a dangerous assumption as they differ so much in implementation, would be good to mention Windows specifically and drop "like" and "Java" altogether. \$\endgroup\$ Jul 21, 2010 at 20:47

7 Answers 7

73
\$\begingroup\$

I don't know much about Java, so this is from the point of view of a .net developer.

The biggest one by far is garbage. The .NET garbage collector on Windows does a fantastic job, and you can get away without baby sitting it for the most part. On the Xbox/Windows Phone 7 that's a different matter. If you get stalls every few frames, garbage collection might be causing you problems. At the moment it triggers after every 1MB allocation.

Here are some tips for dealing with garbage. You shouldn't have to worry about most of these in reality, but they may come in handy one day.

  • Draw the contents of GC.GetTotalMemory() to the screen. This gives you an approximation of the amount of allocated bytes your game uses. If it hardly moves, you're doing OK. If it's going up fast, you have issues.
  • Try to allocate all your heap objects up front. If you don't allocate everything before the game starts, every time you hit a meg of allocations, your going to stall. No allocations, no collections. As simple as that.
  • After loading, call GC.Collect(). If you know most of your big allocations are out the way, it's only nice to let the system know.
  • DO NOT CALL GC.Collect() every frame. It might seem like a good idea, keeping on top of your garbage and all that, but remember the only with worse than a garbage collection is over garbage collection.
  • Look for where your garbage is coming from. There are some common causes like concatenating strings instead of using StringBuilder (beware, StringBuilder isn't a magic bullet, and can still cause allocations!. This means simple operations like adding a number to the end of a string can create surprising amounts of garbage) or using foreach loops over collections that use the IEnumerable interface can also create garbage without you knowing (for example, foreach (EffectPass pass in effect.CurrentTechnique.Passes) is a common one)
  • Use tools like the CLR memory profiler to figure out where memory is being allocated. There are loads of tutorials out there on how to use this tool.
  • When you know where your allocating during gameplay, see if you can use tricks like pooling objects to lower that allocation count.
  • If all else fails, make your collections run faster! The GC on the compact framework follows every reference in your code to figure out what objects are not in use anymore. Refactor your code use less references!
  • Remember to use IDisposable on classes that contain unmanaged resources. You can use these to clean up memory the GC cannot free up itself.

The other thing to think about is floating point performance. While the .NET JITer does a fair amount of processor-specific optimizations, it cannot use SSE or any other SIMD instruction sets to speed up your floating point maths. This can cause quite a big speed difference between C++ and C# for games. If your using mono, they have some special SIMD maths libraries you can take advantage of.

\$\endgroup\$
4
  • \$\begingroup\$ Agree completely. The garbage collector implementations on the "lesser" platforms seems to be complete garbage. \$\endgroup\$
    – Krisc
    Jul 19, 2010 at 10:31
  • \$\begingroup\$ Good post, however with regard to your statement about "allocating heap objects up front" I recommend reading The Truth About Value Types \$\endgroup\$
    – Justin
    Dec 2, 2010 at 8:46
  • \$\begingroup\$ Great point there, when you optimise code its really worth understanding the platform your trying to optimise for. That wasnt really a comment about value types/reference types, any long lived object is not likely to be on the stack. Its really about making sure your getting as much of your allocations done at load time as possible you dont hit that magical 1mb barrier during game play. All the .net implementations that xna targets aslo have a neat guarantee, objects allocated close together in time will be close in space, which can be nice for perf. \$\endgroup\$
    – Cubed2D
    Dec 12, 2010 at 15:18
  • \$\begingroup\$ Also looks like i forgot to mention that the current compact framework on the xbox is allergic to inlineing method calls. Keep that one for your worst case perf scenarios though, using the ref versions of the maths methods looks ugly enough as it is! \$\endgroup\$
    – Cubed2D
    Dec 12, 2010 at 15:20
11
\$\begingroup\$

A tipical performance pitfall is not considering the garbage collector in the design/development of the game. Producing too much garbage can lead to "hiccups" in the game, which happen when the GC runs for a considerable long time.

For C#, using value objects and the "using" statement can alleviate pressure from the GC.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ Additionally you can tell the Garbage Collector to run explicitly if you've just finished an allocate/free heavy loop or if you find you have spare cycles. \$\endgroup\$
    – BarrettJ
    Jul 14, 2010 at 21:03
  • 16
    \$\begingroup\$ The using statement has nothing to do with garbage collection! It's for IDisposable objects - which are for releasing unmanaged resources (ie: those which are not handled by the garbage collector). \$\endgroup\$ Jul 16, 2010 at 11:59
10
\$\begingroup\$

I'd say the biggest problems I've encountered writing games in C# has been the lack of decent libraries. Most I've found are either direct ports, but incomplete, or wrappers over a C++ library that incur a heavy performance penalty for marshaling. (I'm speaking specifically about MOgre and Axiom for the OGRE library, and BulletSharp for the Bullet physics library)

Managed languages (as distinct from Interpreted - neither Java nor C# are actually interpreted anymore) can be just as fast as native languages if you have a good understanding of what actually makes them slow (marshaling, garbage collection). The real problem, I think, is that library developers haven't realized that yet.

\$\endgroup\$
2
  • \$\begingroup\$ That's a good point about C# and Java being managed instead of interpreted... edited my question to make it more accurate :) \$\endgroup\$ Jul 14, 2010 at 19:43
  • 2
    \$\begingroup\$ Marshalling in general is a performance bottleneck, but it helps too to be aware of blittable types - types that can be mapped directly to unmanaged memory without significant performance impact. msdn.microsoft.com/en-us/library/75dwhxf7.aspx \$\endgroup\$ Jul 14, 2010 at 20:28
8
\$\begingroup\$

Like others have said, GC collection pauses are the biggest issue. Using object pools is one typical solution.

\$\endgroup\$
4
\$\begingroup\$

Neither Java nor C# are interpreted. Both of them get compiled into native machine code.

The biggest problem with both of them and games is having to code in such a way that they never garbage collect during game play. The number of hoops you have to jump through to accomplish that nearly outweighs the benefits of using them in the first place. Most of the features that make those language fun to use for application or server programming have to be avoided for game programming otherwise you'll get long pauses during game play as they go off and garbage collect.

\$\endgroup\$
4
  • \$\begingroup\$ Garbage collection can be dealt with, at least in C#, reasonably well, so I wouldn't say that it's a deal-breaker. With proper threading and awareness of program state, you can avoid performance issues. It's still another thing you have to think about, and it does make the managed language a little less managed. \$\endgroup\$
    – Karantza
    Jul 14, 2010 at 20:07
  • 4
    \$\begingroup\$ It's worth noting that in .NET 4, the garbage collector supports background collection for all three generations of objects. This should effectively minimize the performance impact of garbage collection in games. Relevant Link: geekswithblogs.net/sdorman/archive/2008/11/07/… \$\endgroup\$ Jul 14, 2010 at 20:14
  • \$\begingroup\$ Garbage collectors are evolving, and as noted by Mike Strobel, some are already in production that nearly eliminate this pitfall. \$\endgroup\$ Jul 15, 2010 at 19:29
  • 2
    \$\begingroup\$ Neither C# nor Java are compiled to native machine code. C# compiles to MSIL and Java to bytecode. Garbage collection won't give any "long" pauses, but it may give "hiccups". \$\endgroup\$
    – Cloudanger
    Jul 22, 2010 at 15:47
4
\$\begingroup\$

C# and Java are not interpreted. They're compiled to an intermediate bytecode which, after JIT, becomes just as fast as native code (or near enough to be insignificant)

The biggest pitfall I've found is in freeing resources that directly affect user experience. These languages do not automatically support deterministic finalization like C++ does, which if you're not expecting it can lead to things like meshes floating about the scene after you thought they were destroyed. (C# accomplishes deterministic finalization through IDisposable, I'm not sure what Java does.)

Other than that, managed languages are really quite a lot more capable of handling the kind of performance that games require than they get credit for. Well written managed code is much faster than poorly written native code.

\$\endgroup\$
2
  • \$\begingroup\$ Yeah, thanks for the note, already corrected the interpreted/managed thing ;) Also, good point with the meshes floating about the scene. Didn't think of that when thinking about GC problems... \$\endgroup\$ Jul 14, 2010 at 19:53
  • 1
    \$\begingroup\$ IDisposable allows deterministic cleanup of time-critical and unmanaged resources, but does not directly affect finalization or the garbage collector. \$\endgroup\$ Jul 15, 2010 at 19:25
2
\$\begingroup\$

One large pitfall I see from making games with languages like these (or using tools like XNA, TorqueX engine, etc.) is that you will be hard pressed to find a team of good people with the expertise needed to create an equivalent game to what would be fairly easy to find people for in C++ and OpenGL/DirectX.

The game dev industry is still very heavily steeped in C++ as most of the tools and pipelines that are used to push out big or just well polished small games were written in C++, and as far as I know ALL of the official dev kits you can get for XBox, PS3, and Wii are released only with compatibility for C++ (XBox toolset may be more managed nowadays, anyone know more?)

If you want to develop games for consoles right now you pretty much get XNA and C# on the XBox and then only in a side part of the games library called XBox Live Indie Games. Some who win contests etc. get picked to port their game to the real XBox Live Arcade. Other than that plan on making a game for PC.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged .