Memory, Performance, and Speed

[ LiB ]

Memory, Performance, and Speed

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 Garbage Collector

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.

Table 3.10. Commonly Used gc Functions

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.

Table 3.11. set_debug Constants

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 P yMem_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 manage ment on a base level, you should probably hold off on forcibly clearing memory.

Pool Allocators

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.

Figure 3.6. Pymalloc doles out memory requests

graphic/03fig06.gif


NOTE

CAUTION

Pymalloc is meant to be transparent, but it may expose so-far-unknown bugs when used with C exten sions. 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.

Performance and Speed

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 ]


Game Programming with Pyton, Lua and Ruby
Game Programming with Pyton, Lua and Ruby
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 133

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net