[ LiB ] | ![]() ![]() |
In Python, everything is an object, and all objects are allocated in dynamic memory (also called the heap). Because all objects are reference counted, you don't have to worry about freeing memory yourself; this is one of the great benefits of a high-level language. But if you're writing a game, especially a game that has to operate on a PDA or console, you may have to worry about memory allocation and memory fragments.
The first issue is garbage collection. Traditionally, a game's biggest problem is with memory locks that get used up by the game process but not released back to the computerthat is, memory leaks. When a variable goes out of scope or is deleted, it needs to move toward being freed from memory. Problems can arise, however, if a variable is referencing a number of objectsthese extraneous objects may keep the variable from being deleted. The worst-case scenario is when object A is referencing object b and vice versa, in which case neither object can be deleted. Since Python automatically reference-counts each object, this isn't a giant problem. Python's garbage collector will sweep through all objects eventually and clean them up. However, Python's collector will not automatically pick up references to unwanted objects or unclosed files. Failure to delete references to unused objects and leaving unused files open could cause memory leaks to occur. As a rule, all resources in a program should be released as soon as they are no longer needed.
Another potential problem with automatic garbage collection is that as a programmer, you have zero control over when the collector runs. If the collector decides to run while an important level-loading movies sequence is occurring, or during an unusually intense graphic sequence, your game could lose flow or its frame rate could be lowered. One solution to this is to temporarily disable Python's garbage collector while the game is running and then explicitly call it when you want it.
Access Python's garbage collector with the gc (short for Garbage Collection) module. Python's garbage collector is capable of reporting on how many unreachable objects are still allocated memory (this feature is called the Cycle Detector) or how many objects it is currently tracking. These methods (and others) are listed in Table 3.10.
Function | Purpose |
---|---|
collect() | Does a full memory collection |
disable() | Turns automatic garbage collection off |
get_debug() | Gets debug flags |
get_objects | Returns a list of the objects the collector is tracking |
get_referrers | Returns a list of objects that refer to other objects |
get_threshold | Returns current collection threshold |
garbage | Where Python places cyclic garbage with finalizers |
enable() | Turns automatic garbage collection on |
isenabled() | Returns true if automatic garbage collection is on |
set_debug() | Sets debug flags |
set_threshold | Sets the collection threshold |
Several constants are also provided for use with set_debug(), as shown in Table 3.11.
Constant | Use |
---|---|
DEBUG_STATS | Print statistics during collection |
DEBUG_COLLECTABLE | Print information on any collectable objects found |
DEBUG_UNCOLLECTABLE | Print information of any uncollectable objects found |
DEBUG_INSTANCES | Print information about instance objects found |
DEBUG_OBJECTS | Print information about objects other than instance objects found |
DEBUG_SAVEALL | When this flag is set, all unreachable objects found will be appended to garbage rather than being freed |
DEBUG_LEAK | Print information about a leaking program |
You can use the del command to forcibly remove an object from memory. However, del is a finalizer; if you use it on an object, the garbage collector can no longer play with that object, and it loses control. So be sure you know what you are doing.
NOTE
CAUTION
Python's cyclic garbage collector is new as of Python 2.0, and the gc API was added in Version 2.2. Earlier versions of Python will not be as pliable where garbage collection is concerned.
NOTE
TIP
The stack_dealloc function is what Python uses as a destructor to clean up memory blocks after they have been designated. This frees up the memory in PyMem_DEL, the space that holds objects that are decrementing toward deletion. However, if you aren't familiar with c style malloc type commands or memory management on a base level, you should probably hold off on forcibly clearing memory.
Another concern, particularly with consoles, is keeping Python memory allocation contained. Using memory or the garbage collector carelessly can cause Python to swoop in and eat up all a machine's available virtual memory. The trick is to isolate Python into its own memory arena.
Luckily, a few new and upcoming features exist in Python that help out with this issue. Pymalloc, an experimental feature added by Vladimir Marangozov in Version 2.1, is one of these. Pymalloc is a specialized object allocator that actually utilizes C's malloc() (short for memory allocation) function to get large pools of memory and then fill smaller requests for memory from these pools. Since Pymalloc is optional in Version 2.1 and 2.2, you need to include an option to the configure script (in the form of --with-pymalloc) in order to use it. Python Version 2.3 or higher enables it by default.
Pymalloc works by dividing memory requests into size classes (see Figure 3.6). These classes range from eight to 256 bytes and are spaced eight bytes apart. Memory requests lie within 4k pools that hold requests. Pymalloc allocates and deallocates requests for memory from these classes within pools. When deallocating Pymalloc memory classes, the classes can be completely freed (using free()) or released back into their respective pools. When the pools are empty, they are also released back into the memory at large.
NOTE
CAUTION
Pymalloc is meant to be transparent, but it may expose so-far-unknown bugs when used with c extensions. There have already been documented problems using Pymalloc with Python's C API. Use with caution.
Besides Pymalloc, in Version 2.3 Python has deprecated the previous API for dealing with memory and has new functions, some under PyMem, for allocating memory by bytes or type, and some under PyObject for allocating memory specifically for objects.
If you write Python code to do complex numerical work and then compare the results to those done with C++, you will be disappointed. The plain truth is that Python is a slower language. In Python, every variable reference is a hash table lookup, and so is every function call. This cannot compete with C++, in which the locations of variables and functions are decided at compile time.
However, this does not mean that Python is not suitable for game programming; it just means that you have to use it appropriately. For instance, if you are doing string manipulations or working with maps, Python may actually be faster than C++. The Python string manipulation functions are actually written and optimized in C, and the reference-counted object model for Python avoids some of the string copying that can occur with the C++ string class.
And, as I mentioned before, even if you don't think you should write your polygon collision detection code in Python, you may want to write your AI code and game loop in Python and prototype the collision detection. Then, after benchmarking, you can write the collision detection in C++ and expose it to Python. This will make coding much faster for you.
The Python profile module can be used to profile sets of functions. If you had a function called MyFunction stored in MyModule, the function can be imported into new script or the Python interpreter and then profiled by running:
import MyModule profile.run('MyFunction()')
Python's profile module prints a table of all the function calls and each function call's execution time. Python also possesses a useful trace module that can be used to trace the execution of Python scripts.
You'll find that most folks will argue against using Python in games for speed-related issues more than any other. Here are a few performance tips to wrap up the chapter and to keep in mind for dealing with speed issues:
Python has a number of debugging tools to use for benchmarking. If you get used to using them, you can easily get a feel for where things are slow in a given program.
Be careful when using loops, since multiple iterations can easily become memory hogs. Systems calls should be moved outside of loops whenever possible (actually, systems calls should be avoided if at all possible). Try not to instantiate any objects inside of loops; doing so can cause many copies in memory and lots of work for the garbage collector.
Use references instead of actual values when calling values, unless the values are very small.
Avoid passing long argument lists to functions and subroutines. Keep them short and simple.
Avoid reading or writing files line by line. Read them into a buffer instead.
check out all the fun libraries before building a function, and in particular, pay close attention to what Python has built in. Your newly written function is probably slower than the version the community has been using for a few years.
Pay close attention to Chapter 12 in this book and learn how to extend Python in C.
Use the -o switch when compiling to Python to byte-code (o is short for the compiler optimizing mode)
Use aliases for imported functions instead of using the full name. Again, be especially careful when you do things like use full names inside of a loop.
C++ programmers sometimes joke about optimizing their code by making variable names shorter. In Python this may actually work, since Python looks up variables by name at runtime.
Avoid while loops with a loop counter. Instead use range() or xrange(). The Python range() operator is fast because it actually constructs a sequence object over which to iterate.
Avoid heavy use of module-scoped variables. Locally scoped variables are usually faster.
Finally, keep in mind that optimizing code can take a lot of time and effort and isn't always worth it. Also, optimizing may cause other, bigger problems, such as making code harder to maintain, harder to extend, or buggier. Only if a script is running hundreds of times a day, or if the code relies on speed as a requirement, is shaving a few seconds off of it worth the development time.
[ LiB ] | ![]() ![]() |